توی این درس یاد میگیری دستورهایی را که هر روز دستی تایپ میکنی در یک اسکریپت (script) بنویسی: یک فایل متنی ساده که شل (shell) آن را خطبهخط اجرا میکند. میبینی خط اول، یعنی shebang (#!/usr/bin/env bash)، چه میکند، چرا اسکریپت به مجوز اجرا (chmod +x script.sh) نیاز دارد، چه روشهای اجرایی هست (./script.sh، bash script.sh و فقط با اسم) و هر کدام چه فرقی دارند، و با کامنت (#) برای آدمها یادداشت مینویسی. تمرین: اسکریپتی که اسم کاربر، تاریخ و فضای دیسک را چاپ کند.
مسئله: همان دستورها، هر روز
Section titled “مسئله: همان دستورها، هر روز”تا اینجا هر دستور را یک بار توی ترمینال تایپ کردهای و تمام. ولی کارهایی هست که هر روز یا هر هفته تکرار میشود: بکاپ گرفتن، چککردن دیسک و حافظه، ساختن پوشهی یک پروژهی جدید با همان ساختار همیشگی. تایپ دستیِ اینها دو ایراد دارد: کند است و خطا میخورد (یک روز مرحلهی سوم یادت میرود). راهحل این است که دستورها را یک بار در یک فایل بنویسی و هر وقت خواستی همان فایل را اجرا کنی. به چنین فایلی اسکریپت میگویند.
زبانی که در این دوره مینویسی Bash است: همان شلی که در درسهای لینوکس با آن کار میکردی. یعنی هر دستوری که توی ترمینال میزنی، عیناً داخل اسکریپت هم کار میکند. اسکریپتنویسی فقط «همان دستورها + ساختارهای برنامهنویسی» است (متغیر، شرط، حلقه، تابع)، که در درسهای بعد میآیند. کار این درس فقط یک چیز است: اولین اسکریپت را از فایل خالی تا اجرا کامل کنی و بفهمی وسطش چه میگذرد.
تشبیه: دستور پخت
Section titled “تشبیه: دستور پخت”اسکریپت مثل یک دستور پخت است. خود کاغذ غذا نمیپزد؛ یک آشپز باید آن را بردارد و خطبهخط انجام بدهد. فایل اسکریپت همان کاغذ است، bash آشپز است، و خط اولِ کاغذ میگوید «این دستور را به کدام آشپز بدهید». آشپز دقیقاً همان چیزی را که نوشتهای انجام میدهد؛ نه کمتر، نه بیشتر، و بدون اینکه خودش چیزی حدس بزند.
اسکریپت چطور کار میکند؟
Section titled “اسکریپت چطور کار میکند؟”چند اصطلاح که در این درس زیاد میشنوی:
| اصطلاح | یعنی چه |
|---|---|
| اسکریپت (script) | فایل متنی که دستورها را پشتسرهم دارد |
| شل (shell) | برنامهای که دستورها را میخواند و اجرا میکند؛ رایجترینشان Bash است |
| مفسر (interpreter) | برنامهای که متن یک برنامه را میخواند و بدون ترجمهی قبلی، خطبهخط اجرا میکند؛ مثل bash و python3 |
| shebang | خط اول اسکریپت: #! و بعدش مسیر مفسر |
| مجوز اجرا (execute permission) | بیت x روی فایل؛ بدون آن نمیشود فایل را مثل یک برنامه اجرا کرد |
| کد خروج (exit status) | عددی که هر برنامه و اسکریپت هنگام پایان به شل پدرش برمیگرداند؛ ۰ یعنی موفق |
مثالهای عملی
Section titled “مثالهای عملی”همهی مثالها داخل یک پوشهی تمرین اتفاق میافتند. (دستورهای mkdir، ls -l، chmod و cd را از درسهای لینوکس میشناسی.)
مثال ۱: سادهترین اسکریپت، با bash hello.sh
Section titled “مثال ۱: سادهترین اسکریپت، با bash hello.sh”با ویرایشگر دلخواهت (مثلاً nano ~/bashlab/hello.sh) این فایل را بساز:
#!/usr/bin/env bashecho "Hello from my first script"whoamipwdسه دستور پشتسرهم: یک پیام، اسم کاربر و پوشهی فعلی. حالا اجرایش کن:
cd ~/bashlabls -l hello.shecho "--- اجرا با bash:"bash hello.sh-rw-r--r-- 1 ali ali 65 Oct 4 09:31 hello.sh--- اجرا با bash:Hello from my first scriptali/home/ali/bashlabbash hello.sh یعنی «برنامهی bash، این فایل را بخوان و دستورهایش را اجرا کن». دستورها به ترتیب اجرا شدند و خروجی هر کدام پشت سر هم آمد. دقت کن فایل مجوز اجرا ندارد (-rw-r--r--، هیچ x ای نیست) و باز هم کار کرد: چون برنامهای که اجرا میشود bash است و hello.sh فقط یک متن است که bash آن را میخواند؛ برای خواندن فقط مجوز r لازم است.
خط اول (#!/usr/bin/env bash) را فعلاً همانطور بنویس؛ مثال ۳ میگوید چه میکند. برای bash hello.sh اثری ندارد، چون bash آن را یک کامنت میبیند (با # شروع میشود).
مثال ۲: مجوز اجرا و ./hello.sh
Section titled “مثال ۲: مجوز اجرا و ./hello.sh”bash hello.sh کار میکند ولی هر بار باید اسم bash را هم بنویسی. دوست داری اسکریپت مثل یک دستور واقعی اجرا شود: فقط ./hello.sh. اول بدون مجوز امتحان کن:
cd ~/bashlab./hello.sh; echo "کد خروج: $?"echo "--- مجوزهای فعلی:"ls -l hello.shchmod +x hello.shecho "--- بعد از chmod +x:"ls -l hello.sh./hello.sh; echo "کد خروج: $?"bash: ./hello.sh: Permission deniedکد خروج: 126--- مجوزهای فعلی:-rw-r--r-- 1 ali ali 65 Oct 4 09:31 hello.sh--- بعد از chmod +x:-rwxr-xr-x 1 ali ali 65 Oct 4 09:31 hello.shHello from my first scriptali/home/ali/bashlabکد خروج: 0بدون x، شل فایل را اجرا نمیکند: Permission denied و کد خروج ۱۲۶ («پیدا شد، ولی اجرا نشد»). بعد از chmod +x hello.sh سه x اضافه شد (برای مالک، گروه و بقیه) و حالا ./hello.sh اجرا میشود. ./ یعنی «همین پوشه»؛ در مثال ۵ میبینی چرا لازم است.
اگر نمیخواهی به همه مجوز بدهی، chmod راههای دیگری هم دارد (جزئیات در مجوزهای فایل):
cd ~/bashlabcp hello.sh t.shfor mode in u+x 755 700; do chmod 644 t.sh chmod $mode t.sh stat -c "chmod $mode -> %a %A" t.shdonerm t.shchmod u+x -> 744 -rwxr--r--chmod 755 -> 755 -rwxr-xr-xchmod 700 -> 700 -rwx------u+x فقط برای مالک x اضافه میکند (744)، 755 یعنی مالک همهچیز و بقیه خواندن و اجرا (استاندارد برای اسکریپتهای مشترک)، و 700 یعنی فقط مالک (برای اسکریپتهای شخصی یا حساس).
مثال ۳: shebang، «این فایل را با چه برنامهای اجرا کن»
Section titled “مثال ۳: shebang، «این فایل را با چه برنامهای اجرا کن»”وقتی ./hello.sh را میزنی، دیگر کسی نمیگوید فایل را با کدام برنامه باز کند؛ پس خود فایل باید بگوید. خط اولی که با #! شروع میشود همین کار را میکند و فقط مخصوص bash نیست. این هم یک اسکریپت Python:
#!/usr/bin/env python3print("Hello from Python")cd ~/bashlabchmod +x hi.pyecho "--- ./hi.py (shebang: python3):"./hi.pyecho "--- همان فایل را بهزور با bash اجرا کنیم:"bash hi.py--- ./hi.py (shebang: python3):Hello from Python--- همان فایل را بهزور با bash اجرا کنیم:hi.py: line 2: syntax error near unexpected token `"Hello from Python"'hi.py: line 2: `print("Hello from Python")'./hi.py درست کار کرد، چون خط اولش گفت «با python3 بخوانش». ولی bash hi.py یعنی «bash، این فایل را بخوان»، و bash با خط دومِ Python (print("...")) به مشکل خورد. shebang فقط وقتی کار میکند که فایل را مستقیم اجرا کنی (./hi.py)؛ اگر مفسر را خودت جلوی اسم فایل بنویسی، مفسرِ تو حکم است و shebang نادیده گرفته میشود.
حالا یک اشتباه معروف: shebang برابر /bin/sh با کدی که مخصوص bash است. روی Ubuntu و Debian، sh در واقع یک شل سادهتر و سریعتر به اسم dash است، نه bash:
#!/bin/shname="ali"if [[ $name == "ali" ]]; then echo "hello ali"ficd ~/bashlabchmod +x check-sh.shecho "--- sh در این سیستم چیست؟"ls -l /bin/shecho "--- اجرا با shebang برابر sh:"./check-sh.sh; echo "کد خروج: $?"echo "--- shebang را به bash عوض میکنیم:"sed -i '1s|.*|#!/usr/bin/env bash|' check-sh.shhead -n 1 check-sh.sh./check-sh.sh; echo "کد خروج: $?"--- sh در این سیستم چیست؟lrwxrwxrwx 1 root root 4 Mar 31 2024 /bin/sh -> dash--- اجرا با shebang برابر sh:./check-sh.sh: 3: [[: not foundکد خروج: 0--- shebang را به bash عوض میکنیم:#!/usr/bin/env bashhello aliکد خروج: 0[[ ... ]] (که در درس شرطها میخوانی) امکانی مخصوص bash است و dash آن را نمیشناسد: [[: not found. نکتهی ترسناک: کد خروج باز هم ۰ بود؛ یعنی اسکریپتِ خراب بیسروصدا «موفق» گزارش شد. (چرا؟ شرط if شکست خورد و دستور آخر اسکریپت، یعنی fi، موفق بود.) راهحل: برای کدی که امکانات bash دارد همیشه shebang بنویس bash.
شکلهای رایج shebang را در بخش «جدولهای مرجع» مقایسه کردهام. فعلاً این را بدان: #!/usr/bin/env bash یعنی «برنامهی env را اجرا کن و از او بخواه در PATH دنبال bash بگردد»، و برای همین روی هر سیستمی که bash در PATH باشد کار میکند، حتی اگر bash در /bin نباشد.
/bin/bash --version | head -n 1GNU bash, version 3.2.57(1)-release (arm64-apple-darwin26)مثال ۴: کامنتها
Section titled “مثال ۴: کامنتها”هر چه بعد از # بیاید تا آخر همان خط کامنت است و bash نادیدهاش میگیرد. کامنت برای آدمهاست: برای کسی که شش ماه دیگر (یا خودت) اسکریپت را باز میکند.
#!/usr/bin/env bash# ------------------------------------------------# comments.sh: demo of the different comment forms# ------------------------------------------------
echo "one" # a comment at the end of a line# echo "disabled" <- a commented-out command does not runecho "price: 5#" # '#' inside quotes is just textecho color #ff0000 # here '#' starts a commentecho a#b # '#' in the middle of a word is just textecho "two"
: <<'COMMENT'Bash has no real multi-line comment.This here-document trick turns a whole block into a no-op.COMMENT
echo "three"cd ~/bashlabbash comments.shoneprice: 5#colora#btwothreeچند نکته از همین خروجی:
echo "disabled"که جلویش#بود اجرا نشد؛ برای غیرفعالکردن موقت یک دستور،#جلویش بگذار.#فقط اول یک کلمه کامنت شروع میکند.a#bیک کلمهی عادی است و داخل گیومه هم#متن معمولی است. به همین دلیلecho color #ff0000فقطcolorرا چاپ کرد:#ff0000(یک رنگ CSS!) کامنت حساب شد. برای چاپ آن را در گیومه بگذار.- Bash کامنت چندخطی ندارد. معمولترین کار
#روی هر خط است. شکل: <<'COMMENT' ... COMMENTکه آخر فایل دیدی ترفندی است که در درس here-doc میفهمی چرا کار میکند (:یک دستور بیکار است و here-doc متن را به آن میدهد). - shebang هم از دید bash یک کامنت است؛ برای همین
bash hello.shاز خط اول نمینالد.
چه چیزی کامنت کنیم؟ کامنتی که فقط تکرار خود کد است فایده ندارد (# چاپ میکند بالای echo). کامنت خوب چرا را میگوید: «این دستور دو بار تلاش میکند چون API بعد از بیکاری اولین درخواست را رد میکند»، یا بالای فایل میگوید اسکریپت چه میکند و چطور اجرا میشود.
مثال ۵: اجرا فقط با اسم، PATH
Section titled “مثال ۵: اجرا فقط با اسم، PATH”./hello.sh راحتتر از bash hello.sh است ولی هنوز باید جایت را بدانی. ابزارهای واقعی (ls، grep) را از هر پوشهای فقط با اسم اجرا میکنی. چطور اسکریپت خودت هم همینطور شود؟ اول ببین فقط اسم چه میکند:
cd ~/bashlabhello.sh; echo "کد خروج: $?"echo "--- پوشههایی که شل برای پیداکردن دستور میگردد (PATH):"echo "$PATH" | tr ':' '\n'bash: hello.sh: command not foundکد خروج: 127--- پوشههایی که شل برای پیداکردن دستور میگردد (PATH):/usr/local/sbin/usr/local/bin/usr/sbin/usr/bin/sbin/bincommand not found و کد خروج ۱۲۷. شل برای اسمی که / ندارد فقط پوشههای PATH را میگردد، و پوشهی فعلی (.) در آن نیست. این عمدی است: اگر بود، یک فایل ls که کسی توی پوشهی دانلودیات گذاشته بود بهجای ls واقعی اجرا میشد. (PATH را در درس متغیرهای محیطی کامل خواندهای.) پس اسکریپت را به یکی از پوشههای PATH ببر. پوشهی شخصی ~/bin برای همین است:
cd ~/bashlabmkdir -p ~/bincp hello.sh ~/bin/helloexport PATH="$HOME/bin:$PATH"echo "--- شل حالا دستور hello را کجا پیدا میکند؟"type helloecho "--- از هر پوشهای:"cd /tmp && hellocd / && hello--- شل حالا دستور hello را کجا پیدا میکند؟hello is /home/ali/bin/hello--- از هر پوشهای:Hello from my first scriptali/tmpHello from my first scriptali/سه کار کردیم: کپی کردن به ~/bin با اسم بدون .sh (دستورهای واقعی پسوند ندارند؛ .sh فقط قرارداد است و شل به آن کاری ندارد)، اضافهکردن ~/bin به اول PATH، و اجرا. دقت کن pwd داخل اسکریپت هر بار پوشهای را نشان داد که از آن اجرایش کردیم (/tmp و /). برای دائمیشدن، خط export PATH=... را به ~/.bashrc اضافه کن.
مثال ۶: چرا cd داخل اسکریپت ترمینال مرا جابهجا نمیکند؟
Section titled “مثال ۶: چرا cd داخل اسکریپت ترمینال مرا جابهجا نمیکند؟”یک سؤال همیشگی مبتدیها: «اسکریپتی نوشتم که cd میکند، ولی بعد از اجرا هنوز همانجا هستم!». این یک ویژگی است، نه باگ:
#!/usr/bin/env bashecho "script PID: $$"cd /tmpecho "script moved to:"pwdcd ~/bashlabchmod +x where.shecho "shell PID: $$"echo "--- اجرا مثل برنامه:"./where.shecho "بعد از اجرا من اینجام:"; pwdecho "--- با source:"source ./where.shecho "حالا shell من جابهجا شد:"; pwdshell PID: 21041--- اجرا مثل برنامه:script PID: 21106script moved to:/tmpبعد از اجرا من اینجام:/home/ali/bashlab--- با source:script PID: 21041script moved to:/tmpحالا shell من جابهجا شد:/tmp$$ شمارهی پروسهی (PID) خود shell یا اسکریپت جاری است. با ./where.sh شمارهی پروسهی اسکریپت با شمارهی shell من فرق دارد: شل برای اجرای اسکریپت یک پروسهی فرزند میسازد که پوشهی جاری و متغیرهای خودش را دارد؛ فرزند هر چه عوض کند، با تمامشدنش از بین میرود و به پدر نمیرسد. با source ./where.sh (یا . ./where.sh) فایل داخل همین shell اجرا میشود (شمارهی پروسهاش با shell برابر است) و برای همین cd اثر کرد. (همین فرق را در درس متغیرهای محیطی برای فایلهای تنظیمات دیدی؛ برای همین source ~/.bashrc میزنی.)
مثال ۷: اسکریپت واقعی، گزارش صبحگاهی
Section titled “مثال ۷: اسکریپت واقعی، گزارش صبحگاهی”حالا سناریوی ابتدای درس: اسکریپتی که هر صبح وضعیت یک سرور را چاپ میکند. همین کاری که ساختن فایل، shebang، کامنت و مجوز اجرا را کنار هم میگذارد:
#!/usr/bin/env bash# morning-check.sh: a quick health report for one server# Usage: ./morning-check.sh
echo "=== Server report ==="date
echoecho "--- User running this script:"whoami
echoecho "--- Disk (root partition):"df -h /
echoecho "--- Memory:"free -h
echoecho "--- Uptime and load:"uptimecd ~/bashlabchmod +x morning-check.sh./morning-check.sh=== Server report ===Sun Oct 4 09:31:59 UTC 2026
--- User running this script:ali
--- Disk (root partition):Filesystem Size Used Avail Use% Mounted onoverlay 453G 23G 408G 6% /
--- Memory: total used free shared buff/cache availableMem: 3.8Gi 1.4Gi 1.3Gi 13Mi 1.4Gi 2.5GiSwap: 1.0Gi 217Mi 806Mi
--- Uptime and load: 09:31:59 up 1 day, 12:54, 0 user, load average: 0.04, 0.08, 0.18خطِ خالی بین بخشها با echo تنها (بدون آرگومان) ساخته شده؛ یک echo بدون متن فقط یک خط خالی چاپ میکند. هر بخش یک دستور آشنا از درسهای لینوکس است؛ ارزش اسکریپت در یکجا بودن و تکرارپذیری است. اگر دستوری را اشتباه نوشته باشی چه؟ پیش از اجرا میتوانی فقط نحو فایل را بررسی کنی:
cd ~/bashlabcat > broken.sh <<'EOF'#!/usr/bin/env bashif true; then echo "inside if"EOFbash -n broken.sh; echo "کد خروج bash -n: $?"echo "--- فایل سالم:"bash -n morning-check.sh; echo "کد خروج bash -n: $?"broken.sh: line 4: syntax error: unexpected end of fileکد خروج bash -n: 2--- فایل سالم:کد خروج bash -n: 0bash -n (از no execute) اسکریپت را اجرا نمیکند، فقط میخواند و ایراد نحوی را میگیرد: اینجا fi فراموش شده بود و خطا میگوید فایل بیموقع تمام شد. (برای ایرادهای منطقی ابزارهای قویتری هست که در درس دیباگ میخوانی.)
مثال ۸: کد خروج اسکریپت
Section titled “مثال ۸: کد خروج اسکریپت”هر اسکریپت وقتی تمام میشود، مثل هر دستور دیگری یک کد خروج دارد که با echo $? میخوانی؛ ۰ یعنی موفق و هر عدد دیگر یعنی خطا. اما کدام عدد؟
cd ~/bashlabcat > exit-demo.sh <<'EOF'#!/usr/bin/env bashecho "step 1"ls /no/such/direcho "step 2: still runs after the error"EOFchmod +x exit-demo.sh./exit-demo.sh; echo "کد خروج اسکریپت: $?"
cat > exit-last.sh <<'EOF'#!/usr/bin/env bashecho "step 1"ls /no/such/dirEOFchmod +x exit-last.sh./exit-last.sh; echo "کد خروج اسکریپت: $?"
cat > exit-custom.sh <<'EOF'#!/usr/bin/env bashecho "disk is almost full!"exit 3EOFchmod +x exit-custom.sh./exit-custom.sh; echo "کد خروج اسکریپت: $?"step 1ls: cannot access '/no/such/dir': No such file or directorystep 2: still runs after the errorکد خروج اسکریپت: 0step 1ls: cannot access '/no/such/dir': No such file or directoryکد خروج اسکریپت: 2disk is almost full!کد خروج اسکریپت: 3دو چیز مهم:
- Bash بهصورت پیشفرض بعد از خطا متوقف نمیشود.
lsشکست خورد، ولی خط بعدی هم اجرا شد. - کد خروج اسکریپت = کد خروج آخرین دستور. در اسکریپت اول آخرین دستور
echoبود که موفق است، پس اسکریپت با ۰ تمام شد؛ با اینکه وسطش خطا داشت. در اسکریپت دوم آخرین دستورlsبود (کد ۲). وexit 3به هر اسکریپتی اجازه میدهد خودش کد را مشخص کند.
برای چه مهم است؟ چون cron، سیستمهای مانیتورینگ و اسکریپتهای دیگر فقط به همین عدد نگاه میکنند. اسکریپتی که با خطا هم ۰ بدهد، خرابی را پنهان میکند. راهحلش (set -e و بقیهی ابزارها) در درس «مدیریت خطا» است؛ این درس را همینقدر نگه دار: بعد از هر اجرا echo $? را نگاه کن.
پشت پرده: وقتی ./hello.sh را میزنی
Section titled “پشت پرده: وقتی ./hello.sh را میزنی”مراحل دقیق چیست؟
- شل (bashای که تایپ میکنی) یک پروسهی فرزند میسازد (
fork) و از هستهی لینوکس (kernel) میخواهد فایل./hello.shرا اجرا کند (execve). - هسته دو بایت اول فایل را نگاه میکند. اگر
#!باشد (در هگز23 21؛ به آن «عدد جادویی» یا magic number میگویند)، بقیهی خط اول را میخواند: مسیر مفسر و (حداکثر) یک آرگومان. - هسته بهجای خود فایل، مفسر را اجرا میکند و مسیر اسکریپت را به آن میدهد:
/usr/bin/env bash ./hello.sh. envدرPATHدنبالbashمیگردد و خودش را با آن جایگزین میکند. حالا bash فایل را میخواند و خطبهخط اجرا میکند.- وقتی تمام شد، کد خروجش به شل پدر میرسد (
$?).
خودت ببین هسته چه میبیند:
cd ~/bashlabecho "--- دو بایت اول فایل (هگز):"head -c 2 hello.sh | od -An -tx1echo "--- همان دو بایت بهصورت متن:"head -c 2 hello.sh; echoecho "--- خط اول کامل:"head -n 1 hello.shecho "--- ابزار file نوع فایل را از همین خط تشخیص میدهد:"file hello.sh--- دو بایت اول فایل (هگز): 23 21--- همان دو بایت بهصورت متن:#!--- خط اول کامل:#!/usr/bin/env bash--- ابزار file نوع فایل را از همین خط تشخیص میدهد:hello.sh: Bourne-Again shell script, ASCII text executableفقط یک آرگومان. لینوکس هر چه بعد از مسیر مفسر بیاید را یک آرگومان میکند. پس #!/usr/bin/env bash -e به env میگوید دنبال برنامهای به اسم bash -e (با فاصله!) بگردد:
cd ~/bashlabprintf '#!/usr/bin/env bash -e\necho "A: env + bash -e"\n' > opt-a.shprintf '#!/usr/bin/env -S bash -e\necho "B: env -S"\n' > opt-b.shprintf '#!/bin/bash -e\necho "C: direct path"\n' > opt-c.shchmod +x opt-a.sh opt-b.sh opt-c.shfor f in opt-a.sh opt-b.sh opt-c.sh; do ./$f; echo "کد خروج: $?"; done/usr/bin/env: ‘bash -e’: No such file or directory/usr/bin/env: use -[v]S to pass options in shebang linesکد خروج: 127B: env -Sکد خروج: 0C: direct pathکد خروج: 0حالت A خطا داد و env خودش پیشنهاد داد: -S رشته را به چند آرگومان میشکند (حالت B). در حالت C مسیر مستقیم به bash داده شده و -e تنها آرگومان اوست و کار میکند. بهترین راه: shebang را ساده نگه دار و گزینهها را داخل اسکریپت با set بده (درس مدیریت خطا).
بدون shebang چه میشود؟ از داخل bash اتفاقی نمیافتد؛ چون وقتی هسته میگوید «این فایل قابل اجرا نیست» (ENOEXEC)، خود bash فایل را بهعنوان اسکریپت bash اجرا میکند. ولی این رفتارِ bash است، نه قاعدهی سیستم:
cd ~/bashlabprintf 'echo "I have no shebang"\n' > noshe.shchmod +x noshe.shecho "--- از داخل bash:"./noshe.sh; echo "کد خروج: $?"echo "--- از داخل یک برنامهی دیگر (Python):"python3 -B -c 'import subprocess; subprocess.run(["./noshe.sh"])' 2>&1 | tail -n 1--- از داخل bash:I have no shebangکد خروج: 0--- از داخل یک برنامهی دیگر (Python):OSError: [Errno 8] Exec format error: './noshe.sh'mkdir -p /root/shebang-demo && cd /root/shebang-demoprintf 'echo "no shebang"\n' > no.shprintf '#!/usr/bin/env bash\necho "with shebang"\n' > yes.shchmod +x no.sh yes.shecho "--- اجرا مثل یک سرویس systemd، بدون shebang:"systemd-run --unit=lx-noshe --remain-after-exit /root/shebang-demo/no.sh > /dev/null 2>&1sleep 1systemctl status lx-noshe --no-pager | grep -E 'Main PID|Failed to execute' | sed -E 's/^[A-Z][a-z]{2} [0-9]+ [0-9:]+ //'systemctl stop lx-noshe; systemctl reset-failed lx-nosheecho "--- با shebang:"systemd-run --wait --collect -P /root/shebang-demo/yes.sh 2>&1 | grep -E 'with shebang|status='cd / && rm -rf /root/shebang-demo--- اجرا مثل یک سرویس systemd، بدون shebang: Main PID: 18364 (code=exited, status=203/EXEC)server (no.sh)[18364]: lx-noshe.service: Failed to execute /root/shebang-demo/no.sh: Exec format error--- با shebang:with shebangMain processes terminated with: code=exited/status=0systemd-run یک دستور را مثل یک سرویس systemd اجرا میکند. همان فایل no.sh که از bash کار میکرد، از Python با Exec format error و از systemd با کد ۲۰۳ (status=203/EXEC در systemctl status) شکست خورد؛ اگر یک سرویس واقعی را بدون shebang بنویسی همین را میبینی. نتیجه: همیشه shebang بنویس، حتی وقتی «بدون آن هم کار میکند».
جدولهای مرجع
Section titled “جدولهای مرجع”چهار شکل رایج خط اول:
| خط اول | یعنی | چه وقت |
|---|---|---|
#!/usr/bin/env bash |
env در PATH دنبال bash میگردد |
پیشنهاد این دوره: قابلحمل (لینوکس، مک، هر جا bash در PATH باشد) |
#!/bin/bash |
مسیر ثابت bash | سرورهای لینوکس که مطمئنی bash آنجاست؛ ساده و کمی سریعتر |
#!/bin/sh |
شل POSIX (روی Ubuntu و Debian: dash) | فقط وقتی کد شما POSIX خالص است (بدون [[ ]]، آرایه و …) |
#!/usr/bin/env python3 |
هر مفسر دیگر | اسکریپت Python، Perl، Node و … |
چهار راه اجرای اسکریپت:
| روش | مجوز | shebang | پروسهی جدید | کاربرد |
|---|---|---|---|---|
bash script.sh |
فقط r |
نادیده | بله | آزمون سریع، یا فایلی که x ندارد |
./script.sh |
r و x |
استفاده میشود | بله | روش معمول |
فقط script (از PATH) |
r و x |
استفاده میشود | بله | ابزارهای شخصی و سیستمی |
source script.sh یا . script.sh |
فقط r |
نادیده | نه (همین shell) | فایلهای تنظیمات، تغییر دادن shell فعلی |
برای ./script.sh هم r لازم است و هم x: مفسر فایل را با هویت تو میخواند، پس فقطاجرا (--x) کافی نیست.
کدهای خروج که در این درس دیدی:
| کد | یعنی |
|---|---|
0 |
موفق |
1 تا 255 |
خطا؛ معنی دقیقش را هر برنامه خودش مشخص میکند (exit 3 خودمان) |
2 |
ایراد نحو در اسکریپت یا استفادهی اشتباه از دستور (sh arr.sh، ls روی پوشهی ناموجود) |
126 |
فایل پیدا شد ولی قابل اجرا نیست (Permission denied) |
127 |
دستور یا مفسر پیدا نشد (command not found) |
130 |
با Ctrl+C قطع شد (۱۲۸ + شمارهی سیگنال ۲) |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) Permission denied حتی بعد از chmod +x
Section titled “۱) Permission denied حتی بعد از chmod +x”دو دلیل رایج دارد. اول: r را برداشتهای (فقط x). دوم: پارتیشنی که اسکریپت روی آن است با گزینهی noexec mount شده؛ بعضی سرورها /tmp یا /home را برای امنیت اینطور میکنند. در این حالت هیچ فایلی آنجا اجرا نمیشود، ولی bash script.sh هنوز کار میکند:
mkdir -p /mnt/nxmount -t tmpfs -o noexec,size=1m tmpfs /mnt/nxprintf '#!/usr/bin/env bash\necho "ran fine"\n' > /mnt/nx/s.shchmod +x /mnt/nx/s.shls -l /mnt/nx/s.shecho "--- مستقیم:"/mnt/nx/s.sh; echo "کد خروج: $?"echo "--- با bash:"bash /mnt/nx/s.shumount /mnt/nx-rwxr-xr-x 1 root root 36 Oct 4 09:32 /mnt/nx/s.sh--- مستقیم:bash: /mnt/nx/s.sh: Permission deniedکد خروج: 126--- با bash:ran fineفایل -rwxr-xr-x است ولی باز Permission denied. اگر فایل را روی پارتیشن دیگری کپی کنی یا با bash اجرا کنی کار میکند. (با mount | grep noexec پارتیشنهای noexec را مییابی.)
۲) command not found برای اسکریپتی که توی همین پوشه است
Section titled “۲) command not found برای اسکریپتی که توی همین پوشه است”hello.sh بدون ./ ← bash: hello.sh: command not found (مثال ۵). راهحل: ./hello.sh، یا اسکریپت را در پوشهای از PATH بگذار.
۳) فایل از ویندوز آمده: bash\r
Section titled “۳) فایل از ویندوز آمده: bash\r”ویرایشگرهای ویندوز پایان هر خط را CRLF (دو بایت: \r\n) میگذارند، ولی لینوکس فقط \n را میشناسد. نتیجه: \r به آخر خط اول، یعنی اسم مفسر، میچسبد:
cd ~/bashlabprintf '#!/usr/bin/env bash\r\necho "hi from a Windows file"\r\n' > win.shchmod +x win.shfile win.sh./win.sh; echo "کد خروج: $?"echo "--- درستکردن: حذف \\r از آخر خطها"sed -i 's/\r$//' win.shfile win.sh./win.shwin.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators/usr/bin/env: ‘bash\r’: No such file or directory/usr/bin/env: use -[v]S to pass options in shebang linesکد خروج: 127--- درستکردن: حذف \r از آخر خطهاwin.sh: Bourne-Again shell script, ASCII text executablehi from a Windows fileخطا میگوید env دنبال برنامهای به اسم bash\r میگردد (نویسهی \r با خود پیام نشان داده شده). file هم از اول گفته بود with CRLF line terminators. sed -i 's/\r$//' آخر هر خط \r را پاک میکند (ابزار dos2unix هم همین کار را میکند). در ویرایشگرت پایان خط را روی LF بگذار.
۴) sh script.sh بهجای bash script.sh
Section titled “۴) sh script.sh بهجای bash script.sh”روی سرور، اسکریپتی که با bash نوشتهای را با sh اجرا کنی، مفسر dash است، نه bash. امکاناتی مثل آرایهها از کار میافتند:
cd ~/bashlabcat > arr.sh <<'EOF'arr=(one two three)echo "${arr[1]}"EOFecho "--- sh arr.sh:"sh arr.sh; echo "کد خروج: $?"echo "--- bash arr.sh:"bash arr.sh--- sh arr.sh:arr.sh: 1: Syntax error: "(" unexpectedکد خروج: 2--- bash arr.sh:twoراهحل: با bash یا ./script.sh (با shebang درست) اجرا کن.
۵) shebang جای درست نیست یا مفسر اشتباه است
Section titled “۵) shebang جای درست نیست یا مفسر اشتباه است”شرط کار کردن این است که #! دو بایت اولِ فایل باشد؛ حتی یک خط خالی یا فاصله قبلش، آن را از کار میاندازد. و اگر مسیر مفسر غلط باشد، پیام گمراهکنندهای میگیری:
cd ~/bashlabprintf '\n#!/usr/bin/env python3\nprint("hi from python")\n' > lead.pychmod +x lead.pyecho "--- یک خط خالی قبل از shebang:"./lead.py; echo "کد خروج: $?"printf '#!/usr/bin/pythonn\nprint("hi")\n' > typo.pychmod +x typo.pyecho "--- مسیر مفسر غلط (pythonn):"./typo.py; echo "کد خروج: $?"--- یک خط خالی قبل از shebang:./lead.py: line 3: syntax error near unexpected token `"hi from python"'./lead.py: line 3: `print("hi from python")'کد خروج: 2--- مسیر مفسر غلط (pythonn):bash: ./typo.py: cannot execute: required file not foundکد خروج: 127در حالت اول shebang نادیده گرفته شد و bash کد Python را اجرا کرد. در حالت دوم پیام required file not found با اینکه خودِ typo.py وجود دارد، منظورش مفسر است. راهحل: head -n 1 script را نگاه کن و مسیر را با command -v python3 یا command -v bash بسنج.
۶) chmod 777 برای همهچیز
Section titled “۶) chmod 777 برای همهچیز”وقتی چیزی کار نمیکند، وسوسه میشوی chmod 777 بزنی. اسکریپت را نه برای گروه و نه برای بقیه قابلنوشتن نکن: اگر کسی بتواند آن را عوض کند و تو (یا cron، یا root) آن را اجرا کنی، او هر کاری با دسترسی تو میکند. برای اسکریپت معمولاً 755 (یا 700 اگر فقط مال خودت است) کافی است.
یک اسکریپت intro.sh بساز که سه خط چاپ کند: اسم تو، اسم دوره (LoopX) و تاریخ امروز (با دستور date). اول با bash intro.sh اجرایش کن، بعد chmod +x بزن و با ./intro.sh اجرا کن.
دیدن جواب
mkdir -p ~/bashlab && cd ~/bashlabcat > intro.sh <<'EOF'#!/usr/bin/env bash# intro.sh: my first practice scriptecho "Name: Ali"echo "Course: LoopX"dateEOFecho "--- bash intro.sh:"bash intro.shchmod +x intro.shecho "--- ./intro.sh:"./intro.sh--- bash intro.sh:Name: AliCourse: LoopXSun Oct 4 09:31:59 UTC 2026--- ./intro.sh:Name: AliCourse: LoopXSun Oct 4 09:31:59 UTC 2026تمرین اصلی درس: اسکریپت sysinfo.sh بنویس که اسم کاربر، تاریخ و فضای دیسک را چاپ کند؛ هر بخش با یک عنوان. بالای فایل یک کامنت بگذار که بگوید اسکریپت چه میکند. با bash -n نحوش را بررسی کن و بعد اجرایش کن و کد خروجش را نشان بده.
دیدن جواب
mkdir -p ~/bashlab && cd ~/bashlabcat > sysinfo.sh <<'EOF'#!/usr/bin/env bash# sysinfo.sh: prints the current user, the date and the disk usage
echo "--- User:"whoami
echo "--- Date:"date
echo "--- Disk:"df -h /EOFchmod +x sysinfo.shbash -n sysinfo.sh && echo "syntax OK"./sysinfo.shecho "کد خروج: $?"syntax OK--- User:ali--- Date:Sun Oct 4 09:31:59 UTC 2026--- Disk:Filesystem Size Used Avail Use% Mounted onoverlay 453G 23G 408G 6% /کد خروج: 0در درس بعد یاد میگیری خروجی whoami و date را وسط یک جمله بگذاری و اسکریپت کوتاهتر شود.
سه اسکریپت خراب داریم. بدون عوضکردن محتوای اصلیشان، هر سه را درست کن تا هر کدام OK چاپ کنند.
mkdir -p ~/bashlab/fix && cd ~/bashlab/fixprintf '#!/usr/bin/env bash\necho "a: OK"\n' > a.shprintf '#!/usr/bin/env bash\r\necho "b: OK"\r\n' > b.sh && chmod +x b.shprintf '#!/bin/sh\nx=1\nif [[ $x == 1 ]]; then echo "c: OK"; fi\n' > c.sh && chmod +x c.shfor f in a b c; do echo "--- $f.sh:"; ./$f.sh; done--- a.sh:bash: ./a.sh: Permission denied--- b.sh:/usr/bin/env: ‘bash\r’: No such file or directory/usr/bin/env: use -[v]S to pass options in shebang lines--- c.sh:./c.sh: 3: [[: not foundدیدن جواب
سه ایراد مختلف: a.sh مجوز اجرا ندارد، b.sh پایان خطهای ویندوزی دارد و c.sh shebang اش sh است در حالی که کدش bash میخواهد.
cd ~/bashlab/fixchmod +x a.shsed -i 's/\r$//' b.shsed -i '1s|.*|#!/usr/bin/env bash|' c.shfor f in a b c; do ./$f.sh; donea: OKb: OKc: OKیک اسکریپت Bash به اسم hello-sh و یک اسکریپت Python به اسم hello-py در ~/bin بساز، طوری که هر دو فقط با اسم و از هر پوشهای (مثلاً /) اجرا شوند. با file نوع هر دو را نشان بده و ثابت کن اگر bash را روی فایل Python بزنی خطا میگیری. چرا؟
دیدن جواب
mkdir -p ~/bincat > ~/bin/hello-sh <<'EOF'#!/usr/bin/env bashecho "Hello from bash"EOFcat > ~/bin/hello-py <<'EOF'#!/usr/bin/env python3print("Hello from Python")EOFchmod +x ~/bin/hello-sh ~/bin/hello-pyexport PATH="$HOME/bin:$PATH"cd /hello-shhello-pyecho "--- نوع فایلها:"file ~/bin/hello-sh ~/bin/hello-pyecho "--- bash روی فایل Python:"bash ~/bin/hello-py; echo "کد خروج: $?"Hello from bashHello from Python--- نوع فایلها:/home/ali/bin/hello-sh: Bourne-Again shell script, ASCII text executable/home/ali/bin/hello-py: Python script, ASCII text executable--- bash روی فایل Python:/home/ali/bin/hello-py: line 2: syntax error near unexpected token `"Hello from Python"'/home/ali/bin/hello-py: line 2: `print("Hello from Python")'کد خروج: 2file از روی shebang نوع را تشخیص داد و هر دو با اسم اجرا شدند. ولی bash ~/bin/hello-py یعنی «bash، این را بخوان» و shebang نادیده گرفته میشود؛ پس bash سعی میکند کد Python را مثل Bash بخواند و به ایراد نحو میخورد.
آزمونک
Section titled “آزمونک”چرا برای اجرای «bash hello.sh» مجوز اجرا (x) لازم نیست؟
برای ./hello.sh فایل خودش اجرا میشود و به x نیاز دارد؛ ولی در bash hello.sh برنامهی در حال اجرا bash است.
خط #!/usr/bin/env bash در اول اسکریپت چه میکند؟
برای خود bash یک کامنت است (با # شروع میشود) و در bash script.sh نادیده گرفته میشود. مجوز اجرا را فقط chmod میدهد.
اسکریپتی ساختهای ولی chmod +x نزدهای. کدام دستور آن را اجرا میکند؟
./script.sh ← Permission denied (کد ۱۲۶) و script.sh ← command not found (کد ۱۲۷). bash script.sh فقط به مجوز خواندن نیاز دارد.
چرا اسکریپت داخل پوشهی فعلی را باید با ./ اجرا کنی؟
اگر اسکریپت را در پوشهای از PATH (مثل ~/bin) بگذاری، بدون ./ هم اجرا میشود.
اسکریپتی cd /tmp دارد. بعد از ./script.sh پوشهی ترمینال تو چه میشود؟
با source script.sh فایل داخل همین shell اجرا میشود و پوشه عوض میشود.
اسکریپتی سه دستور دارد: echo، ls /no/such/dir (که خطا میدهد) و echo done. کد خروج اسکریپت چند است؟
برای همین باید خطاها را در خود اسکریپت مدیریت کنی (درس مدیریت خطا).
اسکریپتی از ویندوز آمده و ./script.sh خطای «env: bash\r: No such file or directory» میدهد. علت و راهحل؟
ابزار file با «with CRLF line terminators» همین را نشان میدهد؛ dos2unix هم کار را انجام میدهد.
جمعبندی
Section titled “جمعبندی”- اسکریپت یک فایل متنی از دستورهاست که bash خطبهخط اجرا میکند. هر دستوری که در ترمینال کار میکند در اسکریپت هم کار میکند.
- خط اول shebang است،
#!/usr/bin/env bash: وقتی فایل مستقیم اجرا شود، هسته از آن میفهمد با چه مفسری اجرا کند. باید دو بایت اول فایل باشد. همیشه بنویسش؛ بدون آن فقط در bash کار میکند. - برای
./script.shهمrو همxلازم است (chmod +x script.sh)؛bash script.shفقطrمیخواهد و shebang را نادیده میگیرد. اسکریپت داخل پوشهای ازPATHرا بدون./هم میشود اجرا کرد. - اسکریپت در پروسهی فرزند اجرا میشود:
cdو متغیرهایش به shell تو نمیرسد؛ باsource script.shدر همان shell اجرا میشود. - کامنت با
#شروع میشود (فقط اول یک کلمه)؛ چرا را بنویس، نه چه. - کد خروج اسکریپت = کد آخرین دستور (یا
exit N)؛ بعد از هر اجراecho $?.bash -n script.shفقط نحو را بدون اجرا بررسی میکند. - خطاهای رایج:
Permission denied(۱۲۶: نبودنxیاnoexec)،command not found(۱۲۷: فراموشیِ./)،bash\r(فایل ویندوزی)،[[: not found(shebang برابرsh).
| دستور | کاری که میکند |
|---|---|
#!/usr/bin/env bash | خط اول: اجرا با bash (قابلحمل) |
chmod +x script.sh | دادن مجوز اجرا |
./script.sh | اجرای مستقیم (نیاز به x و shebang) |
bash script.sh | اجرا بدون نیاز به x؛ shebang نادیده |
source script.sh / . script.sh | اجرا در همین shell (cd و متغیرها میمانند) |
# توضیح | کامنت؛ تا آخر خط |
bash -n script.sh | بررسی نحو بدون اجرا |
exit 3 | پایان اسکریپت با کد ۳ |
echo $? | کد خروج آخرین دستور |
file script.sh | تشخیص نوع فایل (از روی shebang) |
sed -i 's/\r$//' file | تبدیل CRLF ویندوز به LF |