Amin'sTechLab
2.01K subscribers
572 photos
371 videos
1.29K files
1K links
🔬 Embedded Systems | AI | FPGA | PCB Design
🚀 Exploring tech at the edge of innovation
🧠 Founder of AminTechLab
📍Engineering the future – one bit at a time
Download Telegram
🐍 سه نقطه ... در پایتون فقط برای زیبایی نیست!
شاید در بعضی از کدهای پایتون چیزی شبیه این دیده باشید:
def process_data():
...

در نگاه اول ممکن است فکر کنیم این سه نقطه فقط برای خالی گذاشتن بدنه تابع استفاده شده یا چیزی شبیه pass است؛ اما در پایتون، ... یک مفهوم واقعی دارد.
در واقع ... همان آبجکتی به نام Ellipsis است.
>>> ...
Ellipsis

>>> type(...)
<class 'ellipsis'>

یعنی سه نقطه در پایتون نه کامنت است، نه صرفاً یک علامت نمایشی، بلکه یک آبجکت واقعی از نوع ellipsis محسوب می‌شود.
اما سؤال اصلی اینجاست:
آیا ... همان pass است؟
نه دقیقاً.
pass یک statement خالی است. یعنی وقتی پایتون به آن می‌رسد، هیچ کاری انجام نمی‌دهد و فقط اجازه می‌دهد ساختار کد از نظر نحوی معتبر باقی بماند.
مثلاً:
def process_data():
pass

اینجا pass فقط می‌گوید:
«فعلاً این بخش خالی است، ولی از نظر syntax مشکلی ندارد.»
اما ... یک expression است، یعنی خودش یک مقدار واقعی تولید می‌کند:
x = ...
print(x)

خروجی:
Ellipsis

پس تفاوت مهم اینجاست:
pass

هیچ مقداری تولید نمی‌کند.
ولی:
...

یک آبجکت واقعی به نام Ellipsis است.
یکی از کاربردهای رایج ... این است که در زمان طراحی اولیه کد، جای بخش‌هایی را که هنوز پیاده‌سازی نشده‌اند نگه داریم:
class UserService:
def create_user(self):
...

def delete_user(self):
...

این روش به‌خصوص در نوشتن اسکلت اولیه کلاس‌ها، توابع، اینترفیس‌ها یا کدهایی که قرار است بعداً تکمیل شوند، کاربرد دارد.
البته از نظر خوانایی، اگر فقط می‌خواهید بگویید «اینجا هنوز کاری انجام نمی‌شود»، استفاده از pass همچنان رایج‌تر و واضح‌تر است.
اما Ellipsis فقط برای خالی گذاشتن تابع نیست. در بعضی کتابخانه‌ها، مخصوصاً در حوزه‌هایی مثل NumPy، از ... برای indexing و slicing پیشرفته استفاده می‌شود.
مثلاً:
array[..., 0]

در اینجا ... معنای خاصی دارد و به کتابخانه کمک می‌کند با ابعاد مختلف آرایه‌ها راحت‌تر کار کند.
همچنین در type hinting و stub fileها هم زیاد دیده می‌شود؛ مخصوصاً در فایل‌هایی که فقط ساختار تابع یا کلاس را مشخص می‌کنند، اما پیاده‌سازی واقعی ندارند.
مثلاً:
def connect(host: str, port: int) -> bool: ...

در این حالت، ... یعنی امضای تابع مشخص شده، اما بدنه آن در جای دیگری تعریف می‌شود یا فعلاً اهمیتی ندارد.
پس دفعه بعد که در کد پایتون با ... روبه‌رو شدید، بدانید که این سه نقطه فقط یک جای‌خالی ساده نیست.
... در پایتون همان Ellipsis است؛ یک آبجکت واقعی، با کاربردهایی فراتر از چیزی که در نگاه اول به نظر می‌رسد.

@Amin_TechLab
👾3👍2
🚀 قسمت ۱: افزایش عملکرد CPU در لینوکس با cpupower
اگر از لینوکس استفاده می‌کنید و می‌خواهید در کارهای سنگین، عملکرد پردازنده را افزایش دهید، یکی از ابزارهای کاربردی برای این کار cpupower است.
این ابزار به شما اجازه می‌دهد حالت کاری CPU را تغییر دهید و مشخص کنید پردازنده بیشتر روی مصرف انرژی تمرکز کند یا روی حداکثر عملکرد.
در لینوکس، CPU همیشه با یک فرکانس ثابت کار نمی‌کند. سیستم‌عامل با توجه به میزان فشار کاری، دما، مصرف برق و تنظیمات مدیریت انرژی، فرکانس پردازنده را کم یا زیاد می‌کند.
این رفتار توسط چیزی به نام CPU Governor کنترل می‌شود.
Governor در واقع سیاست کاری پردازنده را مشخص می‌کند.
برای مثال:
powersave
مصرف انرژی را کاهش می‌دهد و معمولاً CPU را در فرکانس پایین‌تری نگه می‌دارد.
ondemand
فرکانس پردازنده را با توجه به نیاز سیستم کم و زیاد می‌کند.
schedutil
در سیستم‌های جدیدتر رایج است و تلاش می‌کند بین مصرف انرژی و عملکرد تعادل ایجاد کند.
performance
پردازنده را در حالت حداکثر عملکرد قرار می‌دهد.
اگر در حال کامپایل پروژه‌های بزرگ، اجرای ماشین مجازی، بازی، رندر، پردازش داده یا بنچمارک هستید، حالت performance می‌تواند باعث افزایش سرعت پاسخ‌دهی سیستم شود.
اما باید توجه داشت که این حالت معمولاً مصرف انرژی و دمای CPU را هم افزایش می‌دهد.
پس اگر از لپ‌تاپ استفاده می‌کنید، بهتر است فقط زمانی که واقعاً به قدرت پردازشی بالا نیاز دارید، این حالت را فعال کنید.
ادامه دارد...

@Amin_TechLab
👍2🔥2
تا امروز خیلی از ما از ابزارهای AI مثل Claude Code، Cursor یا Codex شبیه یک چت‌بات استفاده می‌کردیم:

Prompt می‌نوشتیم، خروجی می‌گرفتیم، ایرادها را اصلاح می‌کردیم و دوباره Prompt جدید می‌دادیم.

اما قدم بعدی احتمالاً چیزی فراتر از Prompt Engineering است:

🔥 Loop Engineering

در این رویکرد، به‌جای نوشتن Promptهای بهتر، یک چرخه کاری طراحی می‌شود که Agentها بتوانند داخل آن کار را جلو ببرند.

Agent می‌تواند تسک‌ها را بررسی کند، کد را تغییر دهد، تست‌ها را اجرا کند، خطاها را اصلاح کند، PRها و Issueها را مدیریت کند و فقط وقتی تصمیم انسانی لازم بود، کار را به انسان ارجاع دهد.

به زبان ساده، مسیر از این مدل:

«من Prompt می‌نویسم، AI جواب می‌دهد»

به این مدل تغییر می‌کند:

«من سیستم را طراحی می‌کنم، Agentها کار را جلو می‌برند.»

در این مدل، نقش برنامه‌نویس حذف نمی‌شود؛ بلکه به طراحی سیستم، تعیین محدودیت‌ها و کنترل کیفیت تغییر می‌کند.

به‌نظرم Loop Engineering یکی از جذاب‌ترین اتفاقات این روزهای AI برای برنامه‌نویس‌هاست.

github.com/cobusgreyling/loop-engineering

@Amin_TechLab
👍51
Amin'sTechLab
🚀 قسمت ۱: افزایش عملکرد CPU در لینوکس با cpupower اگر از لینوکس استفاده می‌کنید و می‌خواهید در کارهای سنگین، عملکرد پردازنده را افزایش دهید، یکی از ابزارهای کاربردی برای این کار cpupower است. این ابزار به شما اجازه می‌دهد حالت کاری CPU را تغییر دهید و مشخص…
⚙️ قسمت ۲: نصب و بررسی وضعیت CPU با cpupower
برای استفاده از ابزار cpupower ابتدا باید بسته مربوط به آن را نصب کنید. نام بسته در توزیع‌های مختلف لینوکس ممکن است متفاوت باشد.
📦 در اوبونتو، دبیان و توزیع‌های مبتنی بر آن‌ها:
sudo apt update
sudo apt install linux-tools-common

در بعضی نسخه‌های اوبونتو ممکن است لازم باشد ابزار مخصوص کرنل فعلی را هم نصب کنید:
sudo apt install linux-tools-$(uname -r)

📦 در فدورا:
sudo dnf install kernel-tools

📦 در Red Hat / RHEL / CentOS:
sudo yum install kernel-tools

یا در نسخه‌های جدیدتر:
sudo dnf install kernel-tools

📦 در آرچ لینوکس و توزیع‌های مبتنی بر آن:
sudo pacman -S cpupower

بعد از نصب، بهتر است قبل از تغییر تنظیمات، وضعیت فعلی پردازنده را بررسی کنید:
cpupower frequency-info

این دستور اطلاعات مهمی درباره CPU نمایش می‌دهد؛ از جمله:
حداقل و حداکثر فرکانس پردازنده
Governor فعال فعلی
درایور مدیریت فرکانس CPU
حالت‌های قابل استفاده برای مدیریت مصرف انرژی
محدوده فرکانسی قابل پشتیبانی توسط پردازنده
در خروجی این دستور معمولاً بخشی با عنوان available cpufreq governors وجود دارد. این بخش نشان می‌دهد سیستم شما از چه حالت‌هایی مثل performance، powersave، ondemand یا schedutil پشتیبانی می‌کند.
بررسی این اطلاعات قبل از اعمال تغییرات مهم است، چون همه سیستم‌ها دقیقاً گزینه‌های یکسانی ندارند.
ادامه دارد...

@Amin_TechLab
👾21👍1🔥1
Amin'sTechLab
⚙️ قسمت ۲: نصب و بررسی وضعیت CPU با cpupower برای استفاده از ابزار cpupower ابتدا باید بسته مربوط به آن را نصب کنید. نام بسته در توزیع‌های مختلف لینوکس ممکن است متفاوت باشد. 📦 در اوبونتو، دبیان و توزیع‌های مبتنی بر آن‌ها: sudo apt update sudo apt install linux…
🔥 قسمت ۳: فعال‌سازی حالت Performance برای CPU در لینوکس
بعد از نصب cpupower، می‌توانید با یک دستور ساده CPU را روی حالت حداکثر عملکرد قرار دهید:
sudo cpupower frequency-set -g performance

با اجرای این دستور، Governor پردازنده روی حالت performance تنظیم می‌شود.
در این حالت، سیستم تلاش می‌کند پردازنده را در سطح بالاتری از توان پردازشی نگه دارد تا برنامه‌ها سریع‌تر پاسخ دهند و پردازش‌های سنگین با تأخیر کمتری انجام شوند.
این حالت برای موقعیت‌هایی مثل موارد زیر مناسب است:
کامپایل پروژه‌های سنگین
رندر گرفتن
اجرای ماشین مجازی
پردازش داده
اجرای بازی
تست و بنچمارک سیستم
اجرای سرویس‌های حساس به تأخیر
اما حالت performance همیشه بهترین انتخاب نیست.
اگر از لپ‌تاپ استفاده می‌کنید، فعال بودن دائمی این حالت می‌تواند باعث افزایش مصرف باتری، بالا رفتن دمای CPU و بیشتر شدن صدای فن شود.
برای برگشت به حالت کم‌مصرف‌تر می‌توانید از دستور زیر استفاده کنید:

sudo cpupower frequency-set -g powersave

اگر سیستم شما از schedutil پشتیبانی می‌کند، این حالت معمولاً انتخاب متعادلی بین عملکرد و مصرف انرژی است:

sudo cpupower frequency-set -g schedutil

جمع‌بندی ساده:
performance یعنی قدرت بیشتر، مصرف بیشتر و دمای بالاتر.
powersave یعنی مصرف کمتر، عملکرد محدودتر و شارژدهی بهتر.
schedutil یا ondemand یعنی تعادل بین سرعت و مصرف انرژی.
پس اگر به حداکثر توان CPU نیاز دارید، performance گزینه مناسبی است؛ اما برای استفاده روزمره، حالت‌های متعادل معمولاً انتخاب بهتری هستند.

@Amin_TechLab
👾3👍21🔥1
💻 یکی از قوانین کمتر شناخته‌شده اما بسیار مهم در زبان C: Strict Aliasing Rule
یکی از رایج‌ترین اشتباهاتی که حتی برنامه‌نویس‌های باتجربه C مرتکب می‌شوند، تبدیل مستقیم یک اشاره‌گر به نوعی دیگر و دسترسی به داده از طریق آن است.
برای مثال:
#include <stdio.h>

int main() {
float f = 3.14f;

int *p = (int *)&f;

printf("%x\n", *p);

return 0;
}

در نگاه اول شاید تصور کنیم فقط داریم بیت‌های متغیر float را به صورت یک عدد صحیح مشاهده می‌کنیم.
اما مشکل اینجاست که طبق استاندارد زبان C، یک شیء باید معمولاً فقط از طریق اشاره‌گری از همان نوع خودش (یا چند استثنای مشخص) خوانده یا نوشته شود.
به این قانون می‌گویند:
Strict Aliasing Rule
کامپایلرها هنگام فعال بودن Optimization فرض می‌کنند که اشاره‌گرهای با نوع‌های متفاوت، هرگز به یک محل از حافظه اشاره نمی‌کنند.
به همین دلیل ممکن است کامپایلر کدی تولید کند که اصلاً انتظارش را ندارید.
مثلاً این کد را ببینید:
int foo(float *f, int *i)
{
*f = 0.0f;
*i = 42;

return *f == 0.0f;
}

اگر f و i در واقع به یک آدرس اشاره کنند (با Cast کردن)، ممکن است انتظار داشته باشید نتیجه تابع تغییر کند.
اما کامپایلر مجاز است فرض کند این دو اشاره‌گر هرگز به یک داده اشاره نمی‌کنند و بر همین اساس کد را بهینه کند.
در نتیجه، خروجی برنامه می‌تواند کاملاً با انتظار شما متفاوت باشد.
نکته جالب اینجاست که ممکن است برنامه:
gcc main.c

کاملاً درست کار کند.
اما فقط با فعال کردن Optimization:
gcc main.c -O2

رفتار برنامه تغییر کند.
دلیلش این نیست که Optimization خراب است؛
بلکه برنامه از ابتدا قوانین استاندارد C را نقض کرده است.
اگر هدفتان مشاهده بیت‌های یک متغیر است، روش صحیح استفاده از memcpy است:
#include <string.h>

float f = 3.14f;
int bits;

memcpy(&bits, &f, sizeof(bits));

یا در صورت نیاز، از unsigned char * برای دسترسی سطح بایت استفاده کنید؛ زیرا استاندارد C این مورد را مجاز می‌داند.
چرا این قانون وجود دارد؟
کامپایلر با دانستن اینکه انواع مختلف روی یک حافظه قرار ندارند، می‌تواند:
داده‌ها را داخل Register نگه دارد.
دسترسی‌های غیرضروری به حافظه را حذف کند.
کد بسیار سریع‌تری تولید کند.
به همین دلیل است که نقض این قانون معمولاً فقط هنگام Optimization خودش را نشان می‌دهد.
در زبان C، هر Castی مجاز نیست؛
گاهی یک Cast ساده می‌تواند باعث شود کامپایلر فرض‌هایی انجام دهد که نتیجه برنامه را کاملاً تغییر دهد.
پس اگر برنامه‌ای بدون Optimization درست کار می‌کند اما با -O2 رفتار عجیبی پیدا می‌کند، یکی از اولین چیزهایی که باید بررسی کنید، نقض Strict Aliasing Rule است. ⚠️

@Amin_TechLab
4👍3👾2
💻 یکی از اشتباهات رایج در زبان C: برگرداندن آدرس یک متغیر Local از تابع (بخش اول)
یکی از باگ‌های خطرناک در زبان C، استفاده از اشاره‌گری است که به یک متغیر محلی (Local Variable) اشاره می‌کند.
مثلاً:
#include <stdio.h>

int *getNumber()
{
int x = 42;

return &x;
}

int main()
{
int *ptr = getNumber();

printf("%d\n", *ptr);

return 0;
}

در نگاه اول شاید تصور کنیم تابع:
return &x;

آدرس متغیر x را برمی‌گرداند و در main می‌توانیم مقدار آن را بخوانیم.
اما یک مشکل مهم وجود دارد.
متغیر:
int x = 42;

یک متغیر Local است.
یعنی فقط در زمان اجرای تابع:
getNumber()

وجود دارد.
وقتی تابع تمام می‌شود، فضای مربوط به آن در Stack دیگر معتبر نیست.
پس اشاره‌گری که برگردانده شده:
ptr

به حافظه‌ای اشاره می‌کند که دیگر مالکیتی روی آن نداریم.
به این حالت می‌گویند:
Dangling Pointer
ممکن است این کد در بعضی سیستم‌ها خروجی زیر را بدهد:
42

و فکر کنید مشکلی وجود ندارد.
اما این فقط یک اتفاق تصادفی است.
در واقع برنامه شما وارد محدوده‌ای شده که استاندارد C هیچ رفتاری را تضمین نمی‌کند.
در بخش بعدی می‌بینیم چرا این نوع باگ‌ها گاهی بدون خطا اجرا می‌شوند و بعد از تغییر یک خط ساده یا فعال کردن Optimization خراب می‌شوند.

@Amin_TechLab
3🔥3👾2
Amin'sTechLab
💻 یکی از اشتباهات رایج در زبان C: برگرداندن آدرس یک متغیر Local از تابع (بخش اول) یکی از باگ‌های خطرناک در زبان C، استفاده از اشاره‌گری است که به یک متغیر محلی (Local Variable) اشاره می‌کند. مثلاً: #include <stdio.h> int *getNumber() { int x = 42; …
💻 چرا Dangling Pointerها در C خیلی خطرناک هستند؟ (بخش دوم)
یکی از دلایلی که این نوع خطاها سخت پیدا می‌شوند این است که برنامه همیشه Crash نمی‌کند.
مثلاً:
char *getName()
{
char name[] = "Embedded";

return name;
}

این کد اشتباه است.
چون آرایه:
char name[]

داخل Stack ساخته می‌شود.
بعد از خروج از تابع:
getName()
|
v
+---------------+
| name[] |
+---------------+

این حافظه دیگر معتبر نیست.
اما اشاره‌گر هنوز همان آدرس قبلی را نگه داشته است.
حالا ممکن است:
printf("%s", getName());

گاهی درست کار کند.
گاهی خروجی خراب بدهد:
Embe@#12

و گاهی برنامه Crash کند.
حتی تغییرهای ساده می‌توانند رفتار برنامه را تغییر دهند:
void test()
{
int buffer[100];
}

اضافه کردن همین تابع ممکن است باعث شود حافظه قبلی Stack دوباره استفاده شود و مقدار اشاره‌گر شما تغییر کند.
مشکل از کامپایلر نیست؛
برنامه شما دارد از حافظه‌ای استفاده می‌کند که دیگر وجود ندارد.
در بخش آخر روش‌های صحیح مدیریت این شرایط را بررسی می‌کنیم.

@Amin_TechLab
2🔥2👾2
💻 روش صحیح مدیریت طول عمر حافظه در زبان C (بخش سوم)
اگر می‌خواهید مقداری بعد از خروج از تابع باقی بماند، نباید آدرس یک متغیر Local را برگردانید.
چند روش صحیح وجود دارد:
روش اول: استفاده از static
char *getName()
{
static char name[] = "Embedded";

return name;
}

متغیرهای static تا پایان اجرای برنامه در حافظه باقی می‌مانند.
روش دوم: استفاده از حافظه Dynamic
#include <stdlib.h>
#include <string.h>

char *getName()
{
char *name = malloc(20);

strcpy(name, "Embedded");

return name;
}

اما مسئولیت آزاد کردن حافظه با شماست:
free(name);

اگر این کار انجام نشود، مشکل دیگری به نام:
Memory Leak
ایجاد می‌شود.
روش سوم: ارسال Buffer توسط Caller
این روش در برنامه‌های Embedded بسیار رایج است:
void getName(char *buffer)
{
strcpy(buffer, "Embedded");
}

در این حالت کسی که حافظه را ساخته، طول عمر آن را هم کنترل می‌کند.
در زبان C همیشه یک سؤال مهم وجود دارد:
این حافظه متعلق به چه کسی است و تا چه زمانی معتبر است؟
بسیاری از خطاهای سخت در Embedded و سیستم‌های Low-Level به دلیل پاسخ اشتباه به همین سؤال ایجاد می‌شوند.
یک Pointer فقط یک آدرس نیست؛
یک قرارداد درباره مالکیت و طول عمر حافظه است. ⚠️

@Amin_TechLab
🔥3👾32👍1
پشت صحنه‌ی یک دستور در لینوکس! (۱/۱۰)
وقتی در لینوکس ترمینال را باز می‌کنید و یک دستور مثل ls یا pwd را اجرا می‌کنید، واقعاً چه اتفاقی می‌افتد؟
قبل از اینکه سراغ شل برویم، باید کمی به گذشته برگردیم.
دهه‌های ۱۹۵۰ و ۱۹۶۰ چیزی به اسم «ترمینال» اصلاً یک پنجره‌ی نرم‌افزاری نبود؛ بلکه یک دستگاه فیزیکی بود که تنها وظیفه‌اش دریافت ورودی از کاربر و نمایش خروجی کامپیوتر بود.
در آن زمان کاربر متن را با صفحه‌کلید وارد می‌کرد، داده‌ها از طریق سیگنال‌های الکتریکی به کامپیوتر مرکزی ارسال می‌شدند و پس از پردازش، پاسخ دوباره به دستگاه برمی‌گشت تا روی رول‌های کاغذ چاپ شود.
این دستگاه‌ها که با نام Teletype (TTY) شناخته می‌شدند، سرعت بالایی هم نداشتند؛ معمولاً چیزی حدود ۱۰ تا ۲۰ کاراکتر در ثانیه چاپ می‌کردند.
چند سال بعد ترمینال‌های ویدیویی معرفی شدند. دیگر خبری از رول‌های کاغذی نبود و خروجی مستقیماً روی نمایشگرهای CRT نمایش داده می‌شد. دستگاه‌هایی مانند DEC VT100 یکی از معروف‌ترین نمونه‌های این نسل بودند و تجربه‌ی کار با کامپیوتر را متحول کردند.

ادامه دارد....

@Amin_Techlab
2🔥2👾2
Amin'sTechLab
پشت صحنه‌ی یک دستور در لینوکس! (۱/۱۰) وقتی در لینوکس ترمینال را باز می‌کنید و یک دستور مثل ls یا pwd را اجرا می‌کنید، واقعاً چه اتفاقی می‌افتد؟ قبل از اینکه سراغ شل برویم، باید کمی به گذشته برگردیم. دهه‌های ۱۹۵۰ و ۱۹۶۰ چیزی به اسم «ترمینال» اصلاً یک پنجره‌ی…
پشت صحنه‌ی یک دستور در لینوکس!! (۲/۱۰)
امروزه دیگر آن دستگاه‌های بزرگ وجود ندارند، اما مفهوم آن‌ها همچنان زنده است.
وقتی برنامه‌ای مثل GNOME Terminal، Konsole یا Windows Terminal را باز می‌کنید، در واقع با یک Terminal Emulator کار می‌کنید؛ نرم‌افزاری که رفتار همان ترمینال‌های قدیمی را شبیه‌سازی می‌کند.
به همین دلیل هنوز هم اصطلاحاتی مانند TTY و PTS را در لینوکس می‌بینیم؛ یادگار روزهایی که ترمینال یک سخت‌افزار واقعی بود.
اما یک نکته‌ی مهم وجود دارد:
ترمینال هیچ دستوری را اجرا نمی‌کند.
وظیفه‌ی آن فقط دریافت ورودی و نمایش خروجی است.
اجرای دستورات بر عهده‌ی برنامه‌ای دیگر به نام Shell است.
شل نه سیستم‌عامل است و نه کرنل؛ فقط یک برنامه است، درست مثل هر برنامه‌ی دیگری.
کلمه‌ی Shell به معنی «پوسته» است؛ لایه‌ای که بین کاربر و کرنل قرار می‌گیرد و ارتباط میان آن‌ها را برقرار می‌کند.
مثلاً وقتی دستور زیر را اجرا می‌کنید:
ls

شل ابتدا بررسی می‌کند که فایل اجرایی ls کجا قرار دارد.
برای این کار از متغیر محیطی PATH استفاده می‌کند:
echo $PATH

خروجی چیزی شبیه این خواهد بود:
/usr/local/bin:/usr/bin:/bin

شل این مسیرها را به‌ترتیب جستجو می‌کند تا فایل اجرایی موردنظر را پیدا کند.
به همین دلیل اگر یک فایل اجرایی داخل پوشه‌ی فعلی داشته باشید، فقط نوشتن نام آن کافی نیست و باید مسیرش را هم مشخص کنید:
./my_app

همین طراحی باعث می‌شود فایل‌های ناشناس داخل پوشه‌ی فعلی به‌صورت تصادفی اجرا نشوند و امنیت سیستم حفظ شود.

ادامه دارد ....

@Amin_TechLab
4👾2
Amin'sTechLab
پشت صحنه‌ی یک دستور در لینوکس!! (۲/۱۰) امروزه دیگر آن دستگاه‌های بزرگ وجود ندارند، اما مفهوم آن‌ها همچنان زنده است. وقتی برنامه‌ای مثل GNOME Terminal، Konsole یا Windows Terminal را باز می‌کنید، در واقع با یک Terminal Emulator کار می‌کنید؛ نرم‌افزاری که رفتار…
پشت صحنه‌ی یک دستور در لینوکس! (۳/۱۰)
ممکن است این سؤال برایتان پیش بیاید که آیا فقط یک شل وجود دارد؟
از آنجایی که شل خودش فقط یک برنامه است، هر توسعه‌دهنده‌ای می‌تواند نسخه‌ی مخصوص خودش را بنویسد. به همین دلیل طی سال‌ها شل‌های مختلفی با امکانات متفاوت به وجود آمده‌اند.
• sh (Bourne Shell)
قدیمی‌ترین شل استاندارد یونیکس که در سال ۱۹۷۷ معرفی شد. اگرچه امکاناتش محدود است، اما هنوز هم پایه‌ی بسیاری از اسکریپت‌های سیستمی محسوب می‌شود.
• Bash (Bourne Again Shell)
محبوب‌ترین شل دنیای لینوکس و انتخاب پیش‌فرض بسیاری از توزیع‌ها. قابلیت‌هایی مثل History، تکمیل خودکار با کلید Tab و امکانات پیشرفته‌ی اسکریپت‌نویسی باعث محبوبیت آن شده است.
• Zsh (Z Shell)
شل مدرنی که این روزها در macOS و بسیاری از سیستم‌های توسعه استفاده می‌شود. اگر نام Oh My Zsh را شنیده باشید، با یکی از معروف‌ترین مجموعه افزونه‌های همین شل آشنا هستید.
• Fish (Friendly Interactive Shell)
اگر به دنبال تجربه‌ای ساده‌تر و کاربرپسندتر باشید، Fish یکی از بهترین انتخاب‌هاست. پیشنهاد خودکار دستورات، رنگ‌بندی مناسب و تنظیمات پیش‌فرض خوب، آن را برای کاربران تازه‌کار و حرفه‌ای جذاب کرده است.
اگر این شل‌ها روی سیستم نصب باشند، کافی است نام آن‌ها را در ترمینال اجرا کنید:
fish

یا
zsh

برای تغییر دائمی شل پیش‌فرض نیز می‌توانید از دستور زیر استفاده کنید:
chsh

در نهایت، همه‌ی این شل‌ها یک هدف مشترک دارند؛ برقراری ارتباط بین شما و سیستم‌عامل، فقط با روش‌ها و امکانات متفاوت.

ادامه دارد...

@Amin_TechLab
4👾2
Amin'sTechLab
پشت صحنه‌ی یک دستور در لینوکس! (۳/۱۰) ممکن است این سؤال برایتان پیش بیاید که آیا فقط یک شل وجود دارد؟ از آنجایی که شل خودش فقط یک برنامه است، هر توسعه‌دهنده‌ای می‌تواند نسخه‌ی مخصوص خودش را بنویسد. به همین دلیل طی سال‌ها شل‌های مختلفی با امکانات متفاوت به…
پشت صحنه‌ی یک دستور در لینوکس! (۴/۱۰)
یکی از زیباترین ایده‌هایی که فلسفه‌ی یونیکس بر پایه‌ی آن شکل گرفته، مفهوم Pipeline است.
اصل ماجرا ساده است:
هر برنامه فقط یک کار را انجام دهد، اما همان یک کار را به بهترین شکل ممکن انجام دهد.

حالا سؤال اینجاست؛ اگر هر برنامه فقط یک وظیفه داشته باشد، چطور کارهای پیچیده انجام می‌شوند؟
پاسخ را شل می‌دهد.
هر برنامه در لینوکس سه کانال استاندارد دارد:
stdin → ورودی برنامه
stdout → خروجی عادی
stderr → خروجی خطا
شل می‌تواند خروجی یک برنامه را مستقیماً به ورودی برنامه‌ی بعدی متصل کند.
مثلاً:
cat /var/log/mylog | grep "error"

در اینجا خروجی دستور cat مستقیماً وارد grep می‌شود و grep فقط خطوطی را نمایش می‌دهد که شامل کلمه‌ی error هستند.
بدون اینکه فایل موقتی ساخته شود یا برنامه‌ی بزرگی نوشته شود.
به همین دلیل ابزارهای یونیکس مثل قطعات لگو هستند؛ هر کدام یک کار انجام می‌دهند و شل آن‌ها را کنار هم قرار می‌دهد تا یک کار بزرگ‌تر ساخته شود.
همین سادگی، یکی از مهم‌ترین دلایل ماندگاری فلسفه‌ی یونیکس است.

ادامه دارد....

@Amin_TechLab
2👾2🔥1
Amin'sTechLab
پشت صحنه‌ی یک دستور در لینوکس! (۴/۱۰) یکی از زیباترین ایده‌هایی که فلسفه‌ی یونیکس بر پایه‌ی آن شکل گرفته، مفهوم Pipeline است. اصل ماجرا ساده است: هر برنامه فقط یک کار را انجام دهد، اما همان یک کار را به بهترین شکل ممکن انجام دهد. حالا سؤال اینجاست؛ اگر هر…
پشت صحنه‌ی یک دستور در لینوکس! (۵/۱۰)
وقتی Enter را فشار می‌دهید، اولین چیزی که به شل می‌رسد چیست؟
از دید ما فقط یک خط متن تایپ شده است؛ اما پشت پرده چندین مرحله اتفاق می‌افتد.
ابتدا ترمینال کلیدهایی که فشار داده‌ایم را دریافت می‌کند.
این داده‌ها از طریق TTY به برنامه‌ای ارسال می‌شوند که در Foreground ترمینال قرار دارد؛ معمولاً همان شل.
در نتیجه شل مستقیماً از صفحه‌کلید چیزی دریافت نمی‌کند، بلکه اطلاعات را از Standard Input خودش می‌خواند.
فرض کنید این دستور را وارد می‌کنیم:
echo myfile

در این لحظه هنوز هیچ برنامه‌ای اجرا نشده است.
شل فقط یک رشته‌ی متنی دریافت کرده است.
حالا باید تصمیم بگیرد:
دستور چیست؟
آرگومان‌ها کدام‌اند؟
آیا این دستور داخلی است یا باید فایل اجرایی پیدا شود؟
به همین دلیل شل فقط یک اجراکننده‌ی برنامه نیست؛ بلکه قبل از هر چیز، یک مفسر (Interpreter) است که باید معنی متن واردشده را درک کند.

ادامه دارد ...
@Amin_TechLab
4👾1
Amin'sTechLab
پشت صحنه‌ی یک دستور در لینوکس! (۵/۱۰) وقتی Enter را فشار می‌دهید، اولین چیزی که به شل می‌رسد چیست؟ از دید ما فقط یک خط متن تایپ شده است؛ اما پشت پرده چندین مرحله اتفاق می‌افتد. ابتدا ترمینال کلیدهایی که فشار داده‌ایم را دریافت می‌کند. این داده‌ها از طریق TTY…
پشت صحنه‌ی یک دستور در لینوکس! (۶/۱۰)
قبل از اجرای هر دستور، شل یک مرحله‌ی بسیار مهم را انجام می‌دهد:
Parsing و Expansion
برای مثال این چهار دستور را ببینید:
echo $HOME
echo "$HOME"
echo '$HOME'
echo *.txt

ظاهر آن‌ها تقریباً مشابه است، اما شل آن‌ها را کاملاً متفاوت تفسیر می‌کند.
شل باید تشخیص دهد:
آیا متغیر محیطی باید جایگزین شود؟
آیا نقل‌قول‌ها مانع Expansion هستند؟
آیا * باید به لیست فایل‌ها تبدیل شود؟
در واقع برنامه‌ی echo هیچ شناختی از $HOME یا *.txt ندارد.
تمام این تبدیل‌ها قبل از اجرای برنامه توسط خود شل انجام می‌شود.
به همین دلیل چیزی که برنامه دریافت می‌کند، الزاماً همان متنی نیست که شما تایپ کرده‌اید.
در حقیقت، شل ابتدا متن خام را به یک دستور قابل فهم تبدیل می‌کند و بعد اجرای آن را آغاز می‌کند.

ادامه دارد...

@Amin_TechLab
2👾2
Amin'sTechLab
پشت صحنه‌ی یک دستور در لینوکس! (۶/۱۰) قبل از اجرای هر دستور، شل یک مرحله‌ی بسیار مهم را انجام می‌دهد: Parsing و Expansion برای مثال این چهار دستور را ببینید: echo $HOME echo "$HOME" echo '$HOME' echo *.txt ظاهر آن‌ها تقریباً مشابه است، اما شل آن‌ها را کاملاً…
پشت صحنه‌ی یک دستور در لینوکس! (۷/۱۰)
بعد از اینکه شل تصمیم گرفت چه برنامه‌ای باید اجرا شود، نوبت به دو مفهوم بسیار مهم در سیستم‌عامل‌های شبه‌یونیکس می‌رسد:
fork و exec
فرض کنید دستور زیر را اجرا می‌کنید:
ls

اولین کاری که شل انجام می‌دهد، ایجاد یک فرآیند جدید است.
این کار با سیستم‌کال fork() انجام می‌شود.
بعد از آن، فرآیند جدید با استفاده از exec() خودش را کاملاً با برنامه‌ی ls جایگزین می‌کند.
در همین زمان، شل اصلی منتظر می‌ماند تا اجرای برنامه تمام شود و سپس دوباره کنترل ترمینال را در اختیار بگیرد.
اما همه‌ی دستورات این مسیر را طی نمی‌کنند.
برخی دستورات اصلاً فایل اجرایی مستقلی ندارند و داخل خود شل پیاده‌سازی شده‌اند؛ به این‌ها Shell Builtins گفته می‌شود.
یکی از بهترین مثال‌ها دستور cd است.
اگر cd یک برنامه‌ی مستقل بود، فقط مسیر کاری همان فرآیند فرزند تغییر می‌کرد و بعد از پایان برنامه، شل همچنان در همان پوشه‌ی قبلی باقی می‌ماند.
به همین دلیل دستوراتی مانند:
cd
pwd
alias
export
توسط خود شل اجرا می‌شوند و نیازی به جستجو در PATH ندارند.

ادامه دارد...

@Amin_TechLab
🔥2👾21
Amin'sTechLab
پشت صحنه‌ی یک دستور در لینوکس! (۷/۱۰) بعد از اینکه شل تصمیم گرفت چه برنامه‌ای باید اجرا شود، نوبت به دو مفهوم بسیار مهم در سیستم‌عامل‌های شبه‌یونیکس می‌رسد: fork و exec فرض کنید دستور زیر را اجرا می‌کنید: ls اولین کاری که شل انجام می‌دهد، ایجاد یک فرآیند جدید…
پشت صحنه‌ی یک دستور در لینوکس! (۸/۱۰)
شل فقط مسئول اجرای برنامه‌ها نیست؛ خودش یک زبان برنامه‌نویسی کامل است.
برای مثال:
name="Ali"
echo "$name"

یا:
if [ -f /etc/passwd ]; then
echo "exists"
fi

و حتی:
for file in *.txt; do
echo "$file"
done

در این مثال‌ها دیگر فقط در حال اجرای یک برنامه نیستیم.
شل متغیر تعریف می‌کند، شرط اجرا می‌کند، حلقه می‌سازد و بر اساس قواعد زبانی خودش تصمیم می‌گیرد چه اتفاقی بیفتد.
به همین دلیل Shell Script فقط مجموعه‌ای از دستورات پشت سر هم نیست؛ بلکه یک زبان اسکریپت‌نویسی واقعی است.
با استفاده از آن می‌توان برنامه‌های مختلف را به هم متصل کرد، روی خروجی آن‌ها تصمیم گرفت، عملیات تکراری را خودکار کرد و حتی ابزارهای نسبتاً پیچیده ساخت.
همین ویژگی است که شل را از یک Command Runner ساده فراتر می‌برد.

ادامه دارد...

@Amin_TechLab
👾31🔥1
Amin'sTechLab
پشت صحنه‌ی یک دستور در لینوکس! (۸/۱۰) شل فقط مسئول اجرای برنامه‌ها نیست؛ خودش یک زبان برنامه‌نویسی کامل است. برای مثال: name="Ali" echo "$name" یا: if [ -f /etc/passwd ]; then echo "exists" fi و حتی: for file in *.txt; do echo "$file" done در این مثال‌ها…
پشت صحنه‌ی یک دستور در لینوکس! (۹/۱۰)
کار شل با اجرای یک برنامه تمام نمی‌شود.
اگر برنامه در Foreground اجرا شده باشد، شل تا پایان اجرای آن منتظر می‌ماند و کنترل ترمینال را دوباره پس می‌گیرد.
به همین دلیل هنگام اجرای برنامه‌های زمان‌بر، تا پایان کار خبری از Prompt جدید نیست.
اما وظایف شل فقط به انتظار کشیدن محدود نمی‌شود.
اگر کاربر کلیدهای Ctrl+C یا Ctrl+Z را فشار دهد، این شل است که باید سیگنال مناسب را مدیریت کند.
همچنین اگر برنامه‌ای را با & در پس‌زمینه اجرا کنیم، باز هم شل مسئول مدیریت آن خواهد بود.
او باید:
وضعیت Jobها را نگه دارد.
پایان اجرای آن‌ها را تشخیص دهد.
در صورت نیاز به کاربر اطلاع دهد.
و هم‌زمان Prompt را برای اجرای دستورات بعدی نمایش دهد.
به همین دلیل شل صرفاً یک اجراکننده‌ی برنامه نیست؛ بلکه مدیر چرخه‌ی اجرای آن‌ها نیز محسوب می‌شود.

ادامه دارد...

@Amin_TechLab
👾31🔥1
Amin'sTechLab
پشت صحنه‌ی یک دستور در لینوکس! (۹/۱۰) کار شل با اجرای یک برنامه تمام نمی‌شود. اگر برنامه در Foreground اجرا شده باشد، شل تا پایان اجرای آن منتظر می‌ماند و کنترل ترمینال را دوباره پس می‌گیرد. به همین دلیل هنگام اجرای برنامه‌های زمان‌بر، تا پایان کار خبری از…
پشت صحنه‌ی یک دستور در لینوکس! (۱۰/۱۰)
اگر تمام این مسیر را مرور کنیم، می‌بینیم پشت یک خط فرمان ساده، دنیای بزرگی پنهان شده است.
ابتدا ترمینال ورودی ما را دریافت می‌کند.
شل آن را می‌خواند، تجزیه و تحلیل می‌کند، متغیرها را گسترش می‌دهد، مسیر برنامه را پیدا می‌کند و تصمیم می‌گیرد چه چیزی باید اجرا شود.
در ادامه، با استفاده از مفاهیمی مانند fork و exec فرآیندهای جدید ایجاد می‌شوند و پس از پایان اجرا، شل دوباره کنترل ترمینال را در اختیار می‌گیرد.
از طرف دیگر، شل خودش نیز یک زبان برنامه‌نویسی کامل است؛ زبانی که امکان تعریف متغیر، شرط، حلقه، توابع و خودکارسازی بسیاری از کارها را فراهم می‌کند.
شاید به همین دلیل است که با وجود ده‌ها محیط گرافیکی مدرن، هنوز هم خط فرمان یکی از قدرتمندترین ابزارهای تعامل با سیستم‌عامل محسوب می‌شود.
شل یک ابزار قدیمی نیست؛ بلکه یکی از بنیادی‌ترین بخش‌های دنیای یونیکس است که بعد از چند دهه، همچنان همان سادگی، انعطاف و قدرت روز اول را حفظ کرده است.

پایان.

@Amin_TechLab
👾31🔥1
خب حالا که ساختار Shell در لینوکس رو باهاش آشنا شدیم .
وقتی خوبیه که Mini-Shell بنویسیم تا دقیقا عملکردش رو درک کنیم .
در ادامه ابتدا این Mini-Shell را به زبان C می‌نویسیم و بعد همان را با زبان Rust بازنویسی میکنیم .

@Amin_TechLab
👾21🔥1
💻 ساخت یک Mini Shell در کمتر از ۵۰ خط کد!
اگر بخواهیم ساده‌ترین نسخه‌ی یک Shell را بنویسیم، فقط به چند قابلیت نیاز داریم:
دریافت یک دستور از کاربر
ساخت یک Process جدید (fork)
اجرای برنامه (execvp)
منتظر ماندن تا پایان اجرای برنامه (waitpid)
کد زیر دقیقاً همین کار را انجام می‌دهد.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
char line[256];

while (1)
{
printf("mini-shell> ");

if (!fgets(line, sizeof(line), stdin))
break;

line[strcspn(line, "\n")] = '\0';

if (strcmp(line, "exit") == 0)
break;

char *argv[32];
int argc = 0;

char *token = strtok(line, " ");

while (token && argc < 31)
{
argv[argc++] = token;
token = strtok(NULL, " ");
}

argv[argc] = NULL;

pid_t pid = fork();

if (pid == 0)
{
execvp(argv[0], argv);
perror("execvp");
exit(EXIT_FAILURE);
}
else
{
waitpid(pid, NULL, 0);
}
}

return 0;
}

این کد چگونه کار می‌کند؟
🔹 fgets()
یک خط از کاربر دریافت می‌کند.
🔹 strtok()
رشته را بر اساس فاصله به آرگومان‌های مختلف تقسیم می‌کند.
برای مثال:
ls -l /home

به این تبدیل می‌شود:
argv[0] = "ls"
argv[1] = "-l"
argv[2] = "/home"

🔹 fork()
از فرآیند فعلی یک نسخه‌ی جدید ایجاد می‌کند.
بعد از این تابع دو Process وجود دارد:
Parent (شل)
Child (برنامه‌ای که قرار است اجرا شود)
🔹 execvp()
فرآیند فرزند را با برنامه‌ی موردنظر جایگزین می‌کند.
اگر بنویسید:
ls -l

در واقع Child دیگر Mini Shell نیست؛ تبدیل به برنامه‌ی ls می‌شود.
🔹 waitpid()
شل اصلی منتظر می‌ماند تا اجرای برنامه تمام شود و سپس دوباره Prompt را نمایش می‌دهد.
این دقیقاً همان چرخه‌ای است که تقریباً تمام Shellهای لینوکس (مانند Bash و Zsh) برای اجرای برنامه‌های خارجی انجام می‌دهند:
Read Command


Parse Arguments


fork()


execvp()


Program Runs


waitpid()


Show Prompt Again

این نمونه فقط یک Shell آموزشی است و قابلیت‌هایی مثل Pipeline (|)، Redirect (>)، متغیرهای محیطی، Wildcard (*)، Job Control، Builtins و Quote Parsing را پیاده‌سازی نمی‌کند؛ اما برای درک نحوه‌ی اجرای دستورات در لینوکس، یکی از بهترین نقطه‌های شروع محسوب می‌شود.

@Amin_TechLab
2👾2🔥1