توی این درس یاد میگیری دادهی متغیر را در اسکریپت نگه داری: متغیر تعریف کنی (name="value") و بخوانی (echo "$name")؛ بفهمی چرا name = "value" با فاصله خطا میدهد؛ فرق کوتیشن دوتایی، تکی و بدون کوتیشن را بدانی و چرا تقریباً همیشه باید "$name" بنویسی؛ خروجی یک دستور را در متغیر بگذاری (command substitution، مثل $(date +%F)) و محاسبه کنی ($((a + b))). تمرین: اسکریپتی که اسم فایل بکاپ را با تاریخ امروز بسازد.
مسئله: اسکریپتی که فقط یک کار ثابت میکند
Section titled “مسئله: اسکریپتی که فقط یک کار ثابت میکند”اسکریپتی که فقط دستورهای ثابت دارد، فقط یک کار ثابت میکند. کار واقعی با دادهی متغیر است: اسم یک فایل، تاریخ امروز، یک عدد، مسیر یک پوشه. و اگر مسیر /var/www/shop را در ده خط اسکریپت نوشته باشی و روزی عوض شود، باید ده جا را عوض کنی و حتماً یکی را جا میاندازی. متغیر (variable) یک اسم است که به یک مقدار وصل میشود: یک بار تعریفش میکنی و هر جا لازم شد با اسم صدایش میزنی.
تشبیه: جعبهی برچسبدار
Section titled “تشبیه: جعبهی برچسبدار”متغیر مثل یک جعبهی برچسبدار است: روی جعبه نوشتهای name و داخلش Ali گذاشتهای. هر جا بنویسی $name یعنی «محتوای جعبهی name را همینجا بریز». نکتهی مهم: شل این جایگزینی را قبل از اجرای دستور انجام میدهد؛ دستوری که اجرا میشود اصلاً نمیداند متغیری در کار بوده. این یک جمله، نصف مشکلات کوتیشن را توضیح میدهد، و در بخش «پشت پرده» میبینی.
متغیرها و کوتیشن: قاعدهها
Section titled “متغیرها و کوتیشن: قاعدهها”| موضوع | قاعده |
|---|---|
| تعریف | name="value" (بدون فاصله دور =) |
| خواندن | $name یا ${name} |
| نام | حرف، عدد و _؛ با عدد شروع نمیشود؛ حروف کوچک و بزرگ فرق دارند |
| قرارداد | متغیرهای اسکریپت با حروف کوچک (backup_dir)، ثابتها و متغیرهای محیطی با حروف بزرگ (BACKUP_DIR) |
| نوع | همهچیز متن است؛ عدد را وقتی محاسبه میکنی بهصورت عدد میخوانند |
| متغیر تعریفنشده | خطا نمیدهد؛ خالی است |
سه نوع کوتیشن:
| شکل | $name و $(...) |
فاصله و * |
نمونه |
|---|---|---|---|
| بدون کوتیشن | جایگزین میشود | شکسته میشود (جدا جدا) | echo $name |
دوتایی "..." |
جایگزین میشود | حفظ میشود (یک تکه) | echo "$name" |
تکی '...' |
جایگزین نمیشود؛ همان متن | حفظ میشود | echo '$name' |
مثالهای عملی
Section titled “مثالهای عملی”همهی کدهای این درس را میتوانی هم مستقیم توی ترمینال بزنی، هم داخل یک اسکریپت بگذاری؛ فرقی ندارد.
مثال ۱: تعریف و خواندن متغیر
Section titled “مثال ۱: تعریف و خواندن متغیر”name="Ali"age=30echo "Hello, $name. You are $age."name="Sara"echo "Now the name is $name"echo "A variable that was never set: [$nothing]"Hello, Ali. You are 30.Now the name is SaraA variable that was never set: []name="Ali" مقدار Ali را در متغیر name میگذارد؛ با $name میخوانیش. دوباره انتساب بدهی، مقدار قبلی عوض میشود. متغیری که هرگز تعریف نشده خطا نمیدهد؛ خالی است (آخرین خط). این راحت است ولی خطرناک هم هست (بخش اشتباهات رایج)، و در درس مدیریت خطا با set -u یاد میگیری از آن جلوگیری کنی.
بزرگترین دام تعریف متغیر: فاصله دور =. در بیشتر زبانها میشود name = "Ali" نوشت، ولی در Bash نه:
name = "Ali"name= "Ali"bash: name: command not foundbash: Ali: command not foundدر حالت اول شل خط را اینطور میبیند: «دستوری به اسم name را با دو آرگومان = و Ali اجرا کن» و چنین دستوری نیست. در حالت دوم name= یک انتساب موقت (برای همان یک دستور) است و دستوری که میخواهد اجرا کند Ali است که باز هم وجود ندارد. قاعده: name="value"، همه چسبیده.
مثال ۲: سه نوع کوتیشن
Section titled “مثال ۲: سه نوع کوتیشن”name="Ali"echo "double: Hello, $name"echo 'single: Hello, $name'echo unquoted: Hello, $nameecho "--- کوتیشن داخل کوتیشن:"echo "He said: \"hi\""echo "It's fine inside double quotes"echo 'He said: "hi" (inside single quotes)'echo "price: \$5"echo 'It'\''s tricky inside single quotes'double: Hello, Alisingle: Hello, $nameunquoted: Hello, Ali--- کوتیشن داخل کوتیشن:He said: "hi"It's fine inside double quotesHe said: "hi" (inside single quotes)price: $5It's tricky inside single quotes- در دوتایی فقط چند کاراکتر خاص معنی دارند:
$(متغیر و جایگزینی)،`(روش قدیمی جایگزینی دستور)،\و خود". برای چاپ خود"یا$جلویشان\میگذاری. - در تکی هیچچیز معنی خاصی ندارد: متن دقیقاً همان است که نوشتهای. هیچ راهی برای گذاشتن
'داخل کوتیشن تکی نیست؛ باید کوتیشن را ببندی،\'بگذاری و دوباره باز کنی ('It'\''s')، یا از دوتایی استفاده کنی. - انتخاب: وقتی میخواهی متغیر یا دستور جایگزین شود، دوتایی؛ وقتی متن باید دقیقاً همانطور بماند (مثلاً یک الگوی
grepیا یک دستورawkکه$دارد)، تکی.
مثال ۳: فاصله و *، شل مقدار را «تکهتکه» میکند
Section titled “مثال ۳: فاصله و *، شل مقدار را «تکهتکه» میکند”وقتی متغیر را بدون کوتیشن میخوانی، شل بعد از جایگزینی دو کار دیگر هم میکند: متن را در فاصلهها میشکند (word splitting) و اگر * یا ? باشد با نام فایلها عوضش میکند (globbing):
msg="a b"echo $msgecho "$msg"echo "--- هر آرگومان را داخل [ ] چاپ میکنیم تا شمارش آرگومانها دیده شود:"printf '[%s]\n' $msgprintf '[%s]\n' "$msg"echo "--- و با *:"mkdir -p ~/bashlab/glob && cd ~/bashlab/glob && touch one.txt two.txtpattern="*.txt"echo $patternecho "$pattern"a ba b--- هر آرگومان را داخل [ ] چاپ میکنیم تا شمارش آرگومانها دیده شود:[a][b][a b]--- و با *:one.txt two.txt*.txtecho $msg فاصلههای اضافی را حذف کرد (a b) چون به echo دو آرگومان a و b داد. printf '[%s]\n' ابزار خوبی برای دیدن این است: هر آرگومان را در یک خط و داخل [ ] چاپ میکند (دو آرگومان در برابر یک آرگومان). و *.txt بدون کوتیشن به نام فایلهای واقعی باز شد. این «دورهی بدون کوتیشن» تقریباً هیچوقت چیزی نیست که بخواهی.
مثال ۴: کابوس واقعی، نام فایل با فاصله
Section titled “مثال ۴: کابوس واقعی، نام فایل با فاصله”فایلی به اسم my report.txt (با فاصله) داری و اسمش را در یک متغیر گذاشتهای:
mkdir -p ~/bashlab/spaces && cd ~/bashlab/spacesfile="my report.txt"echo "important data" > "$file"echo "--- cat بدون کوتیشن:"cat $fileecho "--- cat با کوتیشن:"cat "$file"--- cat بدون کوتیشن:cat: my: No such file or directorycat: report.txt: No such file or directory--- cat با کوتیشن:important dataبدون کوتیشن، cat دو آرگومان my و report.txt گرفت و دنبال دو فایل ناموجود گشت. حالا بدتر: همین پوشه دو فایل دیگر هم دارد که اتفاقاً اسمشان my و report.txt است، و میخواهی my report.txt را پاک کنی:
cd ~/bashlab/spacestouch my report.txtecho "--- قبل از rm:"lsfile="my report.txt"rm $fileecho "--- بعد از rm \$file (بدون کوتیشن):"ls--- قبل از rm:mymy report.txtreport.txt--- بعد از rm $file (بدون کوتیشن):my report.txtrm دو فایل دیگر را پاک کرد (my و report.txt، که شاید مهم بودند) و فایلی که میخواستی هنوز هست. این فقط یک نمونهی کوچک است. قاعدهی طلایی: هر جا متغیر را میخوانی کوتیشن دوتایی بگذار: "$file". استثنای واقعی کم است (مثلاً وقتی عمداً میخواهی متن شکسته شود).
مثال ۵: آکولاد، ${name}
Section titled “مثال ۵: آکولاد، ${name}”اسم متغیر کجا تمام میشود؟ شل تا جایی که حرف، عدد و _ ببیند ادامه میدهد:
name="report"echo "[$name_final]"echo "[${name}_final]"echo "[${name}2026]"echo "[$name-final]"[][report_final][report2026][report-final]$name_final یعنی متغیری به اسم name_final (که وجود ندارد، پس خالی). _ جزو اسم است. با ${name} مرز را مشخص میکنی: ${name}_final. در $name-final مشکلی نیست چون - جزو اسم متغیر نیست. قاعدهی عملی: وقتی بلافاصله بعد از متغیر حرف، عدد یا _ میآید، آکولاد بگذار؛ بقیهی وقتها هر دو شکل یکی است. (آکولاد امکانات بیشتری هم دارد، مثل ${#name} و ${name:-default} که در درس «کار با رشتهها» میآید.)
مثال ۶: جایگزینی دستور، $(...)
Section titled “مثال ۶: جایگزینی دستور، $(...)”میخواهی خروجی یک دستور را در متغیر بگذاری؟ دستور را داخل $(...) بنویس؛ شل آن را اجرا میکند و خروجیاش را جای خودش میگذارد (command substitution):
today=$(date +%F)echo "Today is $today"count=$(ls /etc | wc -l)echo "/etc has $count entries"echo "Current folder name: $(basename "$(pwd)")"echo "Old style (backticks): `date +%F`"echo "--- خروجی چندخطی و کوتیشن:"lines=$(printf 'one\ntwo\nthree\n')echo "$lines"echo $linesecho "--- خطهای خالی انتهای خروجی حذف میشود:"x=$(printf 'a\n\n\n')echo "[$x]"Today is 2026-10-04/etc has 139 entriesCurrent folder name: aliOld style (backticks): 2026-10-04--- خروجی چندخطی و کوتیشن:onetwothreeone two three--- خطهای خالی انتهای خروجی حذف میشود:[a]$(date +%F)تاریخ امروز را به شکلسال-ماه-روزمیدهد (جدولdateپایینتر آمده).- میتوان
$(...)را تو در تو نوشت ($(basename "$(pwd)")) و هر کدام کوتیشن خودش را دارد. - شکل قدیمی با بکتیک (
`date +%F`) هم کار میکند، ولی تو در تو شدنش بد است؛ در کد جدید$(...)بنویس. - اگر خروجی چند خط باشد و کوتیشن نگذاری (
echo $lines)، خطها به هم میچسبند (همان شکستهشدن مثال ۳). - خطهای خالی آخر خروجی حذف میشوند.
مثال ۷: محاسبات، $(( ))
Section titled “مثال ۷: محاسبات، $(( ))”Bash میتواند محاسبهی عدد صحیح هم بکند. داخل $(( )) میتوانی اسم متغیرها را بدون $ هم بنویسی:
a=17b=5echo "a + b = $((a + b))"echo "a - b = $((a - b))"echo "a * b = $((a * b))"echo "a / b = $((a / b)) (integer division)"echo "a % b = $((a % b)) (remainder)"echo "2 ** 10 = $((2 ** 10))"echo "max = $(( a > b ? a : b ))"echo "a > b is $(( a > b )) (1 = true, 0 = false)"echo "--- متغیرها متناند، نه عدد:"echo $a+$becho "--- افزایش و کاهش:"count=0((count++))((count += 5))echo "count = $count"echo "--- اعشار؟ Bash بلد نیست؛ از awk استفاده کن:"echo "17 / 5 = $(awk 'BEGIN { printf "%.2f", 17 / 5 }')"a + b = 22a - b = 12a * b = 85a / b = 3 (integer division)a % b = 2 (remainder)2 ** 10 = 1024max = 17a > b is 1 (1 = true, 0 = false)--- متغیرها متناند، نه عدد:17+5--- افزایش و کاهش:count = 6--- اعشار؟ Bash بلد نیست؛ از awk استفاده کن:17 / 5 = 3.40- عملگرها:
+ - * / % **(توان). تقسیم همیشه صحیح است و عدد اعشاری را دور میاندازد:17 / 5برابر ۳. echo $a+$bفقط متن17+5را چاپ کرد، چون+در متن عادی عملگر نیست. محاسبه فقط داخل$(( ))اتفاق میافتد.((count++))و((count += 5))(بدون$) خودِ محاسبه را بهعنوان یک دستور اجرا میکنند و متغیر را عوض میکنند؛ برای شمارندهها در حلقهها کاربردی است.- مقایسهها (
>،==) داخل$(( ))نتیجهی1(درست) یا0(غلط) میدهند، وشرط ? الف : بمثل زبانهای دیگر است. - اعشار نیاز به ابزار دیگری دارد؛
awkدر همهی سیستمها هست (در درس sed و awk با آن آشنا شدی).
مثال ۸: ساختن اسم با تاریخ، سناریوی بکاپ
Section titled “مثال ۸: ساختن اسم با تاریخ، سناریوی بکاپ”حالا مسئلهی ابتدای درس: اسم فایلی که تاریخ امروز را دارد. دستور date با + یک الگو میگیرد و هر %x را با بخشی از تاریخ عوض میکند:
for code in %F %T %Y %m %d %H %M %S %A %j %s; do printf '%-3s -> %s\n' "$code" "$(date "+$code")"doneecho "--- ساختن اسم:"today=$(date +%F)now=$(date +%F_%H-%M-%S)echo "backup-$today.tar.gz"echo "backup-$now.tar.gz"echo "backup-$(date +%Y%m%d).tar.gz"%F -> 2026-10-04%T -> 09:38:33%Y -> 2026%m -> 10%d -> 04%H -> 09%M -> 38%S -> 33%A -> Sunday%j -> 277%s -> 1791106713--- ساختن اسم:backup-2026-10-04.tar.gzbackup-2026-10-04_09-38-33.tar.gzbackup-20261004.tar.gz%F همان %Y-%m-%d است. اسمِ مرتبشوندهی سال-ماه-روز مهم است: وقتی ls اسمها را الفبایی مرتب میکند، ترتیب زمانی هم درست میشود (برعکس 04-10-2026). اگر در یک روز چند بار بکاپ میگیری، ساعت را هم اضافه کن ($now). مشکل : در اسم فایل (%T) این است که بعضی ابزارها و ویندوز آن را نمیپذیرند؛ برای اسم فایل از - استفاده کن.
مثال ۹: متغیرهای آماده، readonly و unset
Section titled “مثال ۹: متغیرهای آماده، readonly و unset”Bash چند متغیر آماده دارد که لازم نیست تعریفشان کنی:
echo "HOME=$HOME USER=$USER PWD=$PWD UID=$UID"echo "BASH_VERSION=$BASH_VERSION"echo "HOSTNAME=$HOSTNAME"echo "RANDOM: $RANDOM $RANDOM (هر بار عدد دیگری)"echo "SECONDS=$SECONDS (ثانیه از شروع این shell)"echo "--- readonly: ثابت واقعی"readonly PI=3.14echo "PI=$PI"PI=3echo "--- unset: پاککردن متغیر"tmp="temporary"echo "[$tmp]"unset tmpecho "[$tmp]"HOME=/home/ali USER=ali PWD=/home/ali UID=1001BASH_VERSION=5.2.21(1)-releaseHOSTNAME=pcRANDOM: 8340 3700 (هر بار عدد دیگری)SECONDS=0 (ثانیه از شروع این shell)--- readonly: ثابت واقعیPI=3.14bash: PI: readonly variable--- unset: پاککردن متغیر[temporary][]readonly متغیری میسازد که عوضشدنش خطا میدهد؛ برای ثابتهای اسکریپت (مثل مسیر بکاپ) خوب است. unset متغیر را کاملاً پاک میکند. و یادآوری از درس متغیرهای محیطی: متغیری که export نشده فقط در همین shell است و پروسههای فرزند (مثل bash script.sh) آن را نمیبینند.
پشت پرده: شل قبل از اجرا چه میکند؟
Section titled “پشت پرده: شل قبل از اجرا چه میکند؟”وقتی خطی مثل cat $file را میزنی، شل اول خط را بازنویسی میکند و بعد دستور را اجرا میکند. مراحل بازنویسی به این ترتیباند:
خودت ببین شل چه چیزی را واقعاً اجرا میکند. set -x (در درس دیباگ کامل میخوانی) هر دستور را بعد از بازنویسی چاپ میکند، با + در اول خط:
cd ~/bashlab/spacesfile="my report.txt"set -xcat $filecat "$file"set +x+ cat my report.txtcat: my: No such file or directorycat: report.txt: No such file or directory+ cat 'my report.txt'important data+ set +xخط اول (+ cat my report.txt): دستور با دو آرگومان رفت (دو خطای بعدی از cat است). خط دوم (+ cat 'my report.txt'): set -x آرگومانی را که فاصله دارد داخل '...' نشان میدهد تا معلوم شود یک آرگومان است. این ابزار قویترین راه فهمیدن «شل چه دید» است. (set +x آن را خاموش میکند.) چند نکتهی دیگر که از همین ترتیب میآید:
۱) $(...) در یک پروسهی فرزند اجرا میشود. چیزی که داخلش به متغیرها میدهی بیرون نمیآید:
x=1y=$(x=2; echo "inside: x=$x")echo "$y"echo "outside: x=$x"inside: x=2outside: x=1۲) محاسبه با عدد ۶۴ بیتی علامتدار است. از حدش بگذری، بیسروصدا دور میزند:
echo $((2 ** 62))echo $((2 ** 63))echo $((2 ** 64))echo $((9223372036854775807 + 1))4611686018427387904-92233720368547758080-9223372036854775808بزرگترین عدد قابلنمایش ۹٬۲۲۳٬۳۷۲٬۰۳۶٬۸۵۴٬۷۷۵٬۸۰۷ است؛ یکی بیشتر شود، منفی میشود. برای عددهای بزرگ یا اعشاری از ابزار دیگری (awk، bc) استفاده کن.
۳) چرا بهصورت پیشفرض متن؟ چون شل برای اجرای برنامههاست و برنامهها آرگومانِ متنی میگیرند. عدد فقط وقتی عدد است که داخل $(( ))، (( )) یا declare -i باشد:
declare -i num=5num=num+3echo "declare -i: $num"str=5str=str+3echo "بدون declare -i: $str"expr 7 + 3declare -i: 8بدون declare -i: str+310با declare -i انتسابِ num=num+3 محاسبه میشود، بدون آن فقط متن است. expr (آخرین خط) روش قدیمی محاسبه است که در اسکریپتهای قدیمی میبینی؛ برای کد جدید از $(( )) استفاده کن.
جدولهای مرجع
Section titled “جدولهای مرجع”| عملگر | معنی | مثال | نتیجه |
|---|---|---|---|
+ - * |
جمع، تفریق، ضرب | $((17 * 5)) |
85 |
/ |
تقسیم صحیح | $((17 / 5)) |
3 |
% |
باقیمانده | $((17 % 5)) |
2 |
** |
توان | $((2 ** 10)) |
1024 |
++ -- += -= |
افزایش، کاهش، انتساب (در (( ))) |
((count += 5)) |
count پنج بیشتر |
< > == != |
مقایسه | $((a > b)) |
1 یا 0 |
? : |
شرط | $((a > b ? a : b)) |
بزرگتر |
کدهای رایج date (ساختن اسم و برچسب):
| کد | یعنی | مثال |
|---|---|---|
%F |
تاریخ استاندارد (%Y-%m-%d) |
2026-10-04 |
%T |
ساعت (%H:%M:%S) |
09:37:54 |
%Y %m %d |
سال، ماه، روز | 2026 10 04 |
%H %M %S |
ساعت، دقیقه، ثانیه | 09 37 54 |
%A |
اسم روز هفته | Sunday |
%j |
روز از سال | 277 |
%s |
ثانیه از ۱۹۷۰ (برای محاسبهی اختلاف زمان) | 1791106674 |
قواعد کوتیشن بهصورت خلاصه: داخل دوتایی جایگزینی ($) هست؛ داخل تکی نیست؛ بدون کوتیشن جایگزینی هست و شکستن و گسترش * هم هست.
اشتباهات رایج
Section titled “اشتباهات رایج”۱) فاصله دور =، یا $ در انتساب
Section titled “۱) فاصله دور =، یا $ در انتساب”مثال ۱: name = "Ali" ← bash: name: command not found. و $ فقط برای خواندن است؛ در انتساب نمیگذاری:
unset name$name="x"echo "کد خروج: $?"bash: =x: command not foundکد خروج: 127شل اول $name را باز میکند و چون name خالی است، آنچه میماند ="x" است و میخواهد آن را مثل یک دستور اجرا کند. راهحل: name="x".
۲) فراموشکردن کوتیشن دور متغیر
Section titled “۲) فراموشکردن کوتیشن دور متغیر”مثال ۴: cat $file و rm $file با اسم فایل دارای فاصله. راهحل: همیشه "$file". ابزار ShellCheck (در درس دیباگ) این را خودکار پیدا میکند.
۳) کوتیشن تکی وقتی جایگزینی میخواستی
Section titled “۳) کوتیشن تکی وقتی جایگزینی میخواستی”today=$(date +%F)echo 'Backup of $today'echo "Backup of $today"echo 'Today: $(date +%F)'Backup of $todayBackup of 2026-10-04Today: $(date +%F)در کوتیشن تکی متن همانطور میماند و هیچ جایگزینی نمیشود. راهحل: دوتایی.
۴) نام متغیر بلند و آکولاد فراموششده
Section titled “۴) نام متغیر بلند و آکولاد فراموششده”مثال ۵: "$name_final" یعنی متغیر name_final، نه $name بهاضافهی _final. راهحل: "${name}_final".
۵) صفر اول: عدد هشتایی
Section titled “۵) صفر اول: عدد هشتایی”عددی که با 0 شروع شود در محاسبه هشتایی حساب میشود. وقتی ماه یا روز را از date یا از اسم فایل میگیری، 08 و 09 دچار مشکل میشوند:
month=08echo $((month + 1))echo $((10#$month + 1))bash: 08: value too great for base (error token is "08")9خطای اول میگوید 08 برای پایهی هشت بیش از حد بزرگ است. 10# به bash میگوید عدد را در مبنای ۱۰ بخوان. (توجه: Bash بعد از چنین خطایی بقیهی همان خط را اجرا نمیکند؛ برای همین هر echo در یک خط جداست.) یک دام مشابه: متن غیرعددی در محاسبه بیصدا صفر میشود:
a=abcecho $((a + 1))1نتیجه 1 شد، بدون هیچ خطایی؛ چون abc بهعنوان اسم یک متغیر خالی خوانده شد. راهحل: ورودی را قبل از محاسبه بررسی کن (درس شرطها).
۶) متغیر خالی در rm -rf $dir/*
Section titled “۶) متغیر خالی در rm -rf $dir/*”یکی از مشهورترین فاجعههای اسکریپتنویسی. وقتی dir به هر دلیل خالی باشد، $dir/* میشود /*، یعنی همهچیز. برای اینکه خودمان آسیب نبینیم، بهجای rm فقط echo میکنیم:
dir=""echo "--- بدون کوتیشن:"echo rm -rf $dir/* | cut -c1-72echo "--- با کوتیشن (هنوز خطرناک است!):"echo rm -rf "$dir"/* | cut -c1-72echo "--- با محافظ :? (داخل یک اسکریپت؛ متوقف میشود):"cat > ~/bashlab/guard.sh <<'EOF'dir=""echo rm -rf "${dir:?dir is not set}"/*echo "never reached"EOFbash ~/bashlab/guard.shecho "کد خروج: $?"--- بدون کوتیشن:rm -rf /bin /bin.usr-is-merged /boot /dev /etc /home /lib /lib.usr-is-me--- با کوتیشن (هنوز خطرناک است!):rm -rf /bin /bin.usr-is-merged /boot /dev /etc /home /lib /lib.usr-is-me--- با محافظ :? (داخل یک اسکریپت؛ متوقف میشود):/home/ali/bashlab/guard.sh: line 2: dir: dir is not setکد خروج: 1دقت کن که کوتیشن اینجا نجات نمیدهد. ${dir:?پیام} (در درس کار با رشتهها میآید) اگر dir خالی باشد، پیام میدهد و اسکریپت را متوقف میکند. راه کلیتر set -u (متغیر تعریفنشده خطا باشد) و یک بررسی صریح ([[ -n "$dir" ]]) قبل از هر دستور مخرب است.
اسکریپت greet.sh را بنویس: دو متغیر name و course (مقدار LoopX) تعریف کن و جملهی Hello, Ali! Welcome to LoopX. را با کوتیشن دوتایی چاپ کن. بعد همان جمله را با کوتیشن تکی چاپ کن تا ببینی چه میشود.
دیدن جواب
mkdir -p ~/bashlab && cd ~/bashlabcat > greet.sh <<'EOF'#!/usr/bin/env bash# greet.sh: variables and the two kinds of quotesname="Ali"course="LoopX"echo "Hello, $name! Welcome to $course."echo 'Hello, $name! Welcome to $course.'EOFchmod +x greet.sh./greet.shHello, Ali! Welcome to LoopX.Hello, $name! Welcome to $course.تمرین اصلی درس: اسکریپت backup.sh که اسم فایل بکاپ را با تاریخ امروز بسازد. پوشهی ~/bashlab/data را (با چند فایل نمونه) با tar در فایل data-backup-تاریخ.tar.gz فشرده کند، اسم و اندازهی فایل را چاپ کند و محتوای آرشیو را نشان بدهد. مسیرها و اسمها را در متغیر بگذار.
دیدن جواب
mkdir -p ~/bashlab/data && cd ~/bashlabecho "alpha" > data/a.txtecho "beta" > data/b.txtcat > backup.sh <<'EOF'#!/usr/bin/env bash# backup.sh: archive a folder into a file named with today's date
source_dir="$HOME/bashlab/data"backup_dir="$HOME/bashlab"today=$(date +%F)backup_file="$backup_dir/data-backup-$today.tar.gz"
echo "Backing up: $source_dir"tar -czf "$backup_file" -C "$(dirname "$source_dir")" "$(basename "$source_dir")"echo "Created: $backup_file"ls -lh "$backup_file"echo "Contents:"tar -tzf "$backup_file"EOFchmod +x backup.sh./backup.shBacking up: /home/ali/bashlab/dataCreated: /home/ali/bashlab/data-backup-2026-10-04.tar.gz-rw-r--r-- 1 ali ali 170 Oct 4 09:38 /home/ali/bashlab/data-backup-2026-10-04.tar.gzContents:data/data/b.txtdata/a.txtدقت کن همهی متغیرها توی کوتیشناند و اسم فایل از $today ساخته شده است. اگر فردا دوباره اجرا کنی، فایل جدیدی با اسم دیگر میماند.
داخل یک پوشه سه فایل notes.txt، my notes.txt و old notes.txt بساز. با یک متغیر و بدون کوتیشن my notes.txt را به my notes.bak کپی کن و خطا را ببین. بعد درستش کن.
دیدن جواب
mkdir -p ~/bashlab/fix && cd ~/bashlab/fixecho "n" > notes.txt; echo "m" > "my notes.txt"; echo "o" > "old notes.txt"src="my notes.txt"echo "--- بدون کوتیشن:"cp $src "my notes.bak"echo "کد خروج: $?"echo "--- با کوتیشن:"cp "$src" "my notes.bak"echo "کد خروج: $?"cat "my notes.bak"--- بدون کوتیشن:cp: target 'my notes.bak': No such file or directoryکد خروج: 1--- با کوتیشن:کد خروج: 0mcp $src "my notes.bak" برای شل میشود cp my notes.txt "my notes.bak": سه آرگومان. وقتی cp بیش از دو آرگومان بگیرد، آخری را «پوشهی مقصد» حساب میکند و چون چنین پوشهای نیست خطا میدهد؛ هیچچیز کپی نشد.
اسکریپتی بنویس که بگوید از شروع امروز چند ثانیه گذشته و آن را به شکل HH:MM چاپ کند (از date +%s، محاسبهی $(( )) و جایگزینی دستور تو در تو استفاده کن). ثابت کن جوابت با date +%H:%M برابر است. (این تمرین date -d دارد که فقط در لینوکس هست؛ روی مک کار نمیکند.)
دیدن جواب
cat > ~/bashlab/since-midnight.sh <<'EOF'#!/usr/bin/env bash# since-midnight.sh: seconds since 00:00 today, shown as HH:MMnow=$(date +%s)midnight=$(date -d 00:00 +%s)seconds=$(( now - midnight ))hours=$(( seconds / 3600 ))minutes=$(( (seconds % 3600) / 60 ))printf 'Seconds since midnight: %d\n' "$seconds"printf 'As HH:MM: %02d:%02d\n' "$hours" "$minutes"printf 'date says: %s\n' "$(date +%H:%M)"EOFbash ~/bashlab/since-midnight.shSeconds since midnight: 34713As HH:MM: 09:38date says: 09:38$(date -d 00:00 +%s) ثانیهی نیمهشب امروز را میدهد و تفاضلش با حالا همان جواب است. printf با %02d عدد را با صفر اول (دو رقمی) چاپ میکند.
آزمونک
Section titled “آزمونک”کدام یک خروجی دستور date +%F را در متغیر today میگذارد؟
today=date +%F یعنی دستور date را با متغیر موقت today اجرا کن؛ کوتیشنها (دوتایی یا تکی) خود متن «date +%F» را در متغیر میگذارند. فقط $(date +%F) خروجی دستور را جایگزین میکند.
کدام انتساب در Bash درست است؟
دور = نباید فاصله باشد و در انتساب $ نمیگذاریم.
name="Ali" و بعد echo 'Hello, $name' چه چاپ میکند؟
در کوتیشن تکی هیچ جایگزینی انجام نمیشود.
file="my report.txt" و دستور cat $file چرا خطا میدهد؟
راهحل: cat "$file".
name="report" و echo "$name_final" چه چاپ میکند و چرا؟
درست: "${name}_final".
echo $((7 / 2)) چه چاپ میکند؟
تقسیم در Bash صحیح است و اعشار را دور میاندازد؛ برای اعشار از awk استفاده کن.
month=08 و echo $((month + 1)) چه میشود؟
درست: $((10#$month + 1)).
جمعبندی
Section titled “جمعبندی”- متغیر:
name="value"(همه چسبیده، بدون فاصله دور=) و خواندن با"$name". همهچیز متن است؛ متغیر تعریفنشده خالی است (نه خطا). - کوتیشن دوتایی جایگزینی میکند ولی مقدار را یک تکه نگه میدارد؛ تکی هیچ جایگزینی نمیکند؛ بدون کوتیشن مقدار را در فاصله میشکند و
*را به نام فایلها باز میکند. قاعدهی عملی: همیشه"$var". ${name}وقتی لازم است که بلافاصله بعد از متغیر حرف، عدد یا_بیاید ("${name}_backup").$(command)خروجی دستور را جایگزین میکند (در یک پروسهی فرزند؛ خطهای خالی انتها حذف میشود).$(date +%F)تاریخ امروز برای اسم فایل است.$((expr))محاسبهی عدد صحیح است (+ - * / % **)؛ تقسیم صحیح؛ صفر اول یعنی هشتایی (10#$nراهحل است)؛ اعشار را بهawkبسپار.- شل خط را قبل از اجرا بازنویسی میکند (آکولاد،
~، متغیر و دستور، شکستن،*، حذف کوتیشن)؛ باset -xمیبینی چه اجرا شد. کوتیشن دور متغیر خالی درrm -rf "$dir"/*نجاتت نمیدهد؛${dir:?}،set -uو بررسی صریح لازم است.
| دستور | کاری که میکند |
|---|---|
name="value" | تعریف متغیر (بدون فاصله دور =) |
echo "$name" | خواندن متغیر، همیشه با کوتیشن دوتایی |
echo '$name' | متن دقیق، بدون جایگزینی |
"${name}_backup" | آکولاد برای مشخصکردن مرز اسم |
today=$(date +%F) | خروجی دستور در متغیر |
$((a + b)) $((a * b)) | محاسبهی عدد صحیح |
((count++)) ((count += 5)) | تغییر شمارنده |
$((10#$month + 1)) | محاسبه با عدد دارای صفر اول |
date +%F_%H-%M-%S | تاریخ و ساعت برای اسم فایل |
readonly PI=3.14 unset name | ثابت واقعی / پاککردن متغیر |
printf '[%s]\n' $var | دیدن اینکه شل متغیر را چند تکه کرد |
${dir:?پیام} | اگر خالی بود، پیام بده و متوقف شو |