توی این درس یاد میگیری چند مقدار را در یک متغیر نگه داری. آرایهی ساده (arr=(a b c)) را میسازی و با ${arr[0]}، همه را با "${arr[@]}" و تعدادشان را با ${#arr[@]} میخوانی؛ عضو اضافه و حذف و برش میدهی؛ میفهمی چرا "${arr[@]}" باید کوتیشن داشته باشد و $arr فقط عضو اول است. سپس آرایهی انجمنی (declare -A map) را میبینی، یعنی جدول «کلید ← مقدار» برای شمارش و جستوجو. تمرین: لیست سرورها را در آرایه بگذار و به هر کدام ping بزن.
مسئله: متغیرِ «یک چیز» کافی نیست
Section titled “مسئله: متغیرِ «یک چیز» کافی نیست”تا اینجا هر متغیر یک مقدار داشت. ولی کار واقعی پر از فهرست است: سرورهایی که باید چک شوند، فایلهایی که باید بایگانی شوند، آرگومانهایی که باید به یک دستور برسند. راهحل بینظم: server1="..."، server2="..."، server3="..." و کدی که هر سه را جدا مینویسد. یا یک رشتهی «a b c» که در فاصلهها میشکند و با اولین اسم دارای فاصله خراب میشود (درس متغیرها). آرایه راه درست است: یک متغیر، چند عضو، هر عضو با اندیسش.
تشبیه: قفسهی شمارهدار و دفترچهی تلفن
Section titled “تشبیه: قفسهی شمارهدار و دفترچهی تلفن”آرایهی ساده مثل یک قفسهی شمارهدار است: خانهی ۰، خانهی ۱، خانهی ۲ و… در هر خانه یک چیز. آرایهی انجمنی مثل دفترچهی تلفن است: بهجای شمارهی خانه، با اسم (کلید) پیدایش میکنی («سارا» ← ۰۹۱۲…). اولی برای فهرستهای مرتب، دومی وقتی میخواهی چیزی را با اسمش پیدا کنی.
دو نوع آرایه
Section titled “دو نوع آرایه”| آرایهی ساده (indexed) | آرایهی انجمنی (associative) | |
|---|---|---|
| تعریف | arr=(a b c) |
declare -A map (حتماً) |
| کلید | عدد (از ۰) | هر متن |
| یک عضو | ${arr[1]} |
${map[key]} |
| همهی مقدارها | "${arr[@]}" |
"${map[@]}" |
| همهی کلیدها | "${!arr[@]}" (اندیسها) |
"${!map[@]}" |
| تعداد | ${#arr[@]} |
${#map[@]} |
| ترتیب | ترتیب اندیس | بدون ترتیب مشخص |
| نسخهی Bash | همه | ۴ به بعد |
مثالهای عملی
Section titled “مثالهای عملی”مثال ۱: ساختن و خواندن
Section titled “مثال ۱: ساختن و خواندن”fruits=(apple banana "cherry pie")echo "first: ${fruits[0]}"echo "third: ${fruits[2]}"echo "last: ${fruits[-1]}"echo "count: ${#fruits[@]}"echo "length of the third element: ${#fruits[2]}"echo "all: ${fruits[@]}"echo "--- declare -p ساختار واقعی را نشان میدهد:"declare -p fruitsecho "--- \$fruits بدون اندیس فقط عضو اول است:"echo $fruitsecho ${#fruits}first: applethird: cherry pielast: cherry piecount: 3length of the third element: 10all: apple banana cherry pie--- declare -p ساختار واقعی را نشان میدهد:declare -a fruits=([0]="apple" [1]="banana" [2]="cherry pie")--- $fruits بدون اندیس فقط عضو اول است:apple5- اعضا را با فاصله داخل پرانتز مینویسی؛ عضو دارای فاصله باید در کوتیشن باشد (
"cherry pie"). - اندیس از ۰ شروع میشود؛
${fruits[-1]}آخرین عضو است (Bash ۴.۳ به بعد). ${fruits[@]}همهی عضوها؛${#fruits[@]}تعداد عضوها؛${#fruits[2]}طول متن عضو سوم (درس «کار با رشتهها»).declare -pبهترین ابزار برای دیدن آرایه است: اندیسها و مقدارها را دقیقاً نشان میدهد.$fruitsبدون اندیس یعنی${fruits[0]}: فقط عضو اول. و${#fruits}طول همان عضو اول (۵ =apple) است، نه تعداد عضوها. این دو اشتباه رایجاند.
مثال ۲: افزودن، تغییر، حذف و برش
Section titled “مثال ۲: افزودن، تغییر، حذف و برش”fruits=(apple banana "cherry pie")fruits+=(date elderberry)echo "after +=: ${fruits[@]} (count ${#fruits[@]})"fruits[1]=blueberryecho "after fruits[1]=..: ${fruits[@]}"echo "slice (from 1, two items): ${fruits[@]:1:2}"unset 'fruits[1]'echo "--- بعد از unset 'fruits[1]' (یک «سوراخ» میماند):"declare -p fruitsecho "indices: ${!fruits[@]} count: ${#fruits[@]}"fruits=("${fruits[@]}")echo "--- بعد از فشردهکردن مجدد:"declare -p fruitsafter +=: apple banana cherry pie date elderberry (count 5)after fruits[1]=..: apple blueberry cherry pie date elderberryslice (from 1, two items): blueberry cherry pie--- بعد از unset 'fruits[1]' (یک «سوراخ» میماند):declare -a fruits=([0]="apple" [2]="cherry pie" [3]="date" [4]="elderberry")indices: 0 2 3 4 count: 4--- بعد از فشردهکردن مجدد:declare -a fruits=([0]="apple" [1]="cherry pie" [2]="date" [3]="elderberry")+=(...)عضو به انتها اضافه میکند.${fruits[@]:1:2}یک برش است: از اندیس ۱، دو عضو.unset 'arr[1]'عضو را حذف میکند ولی اندیسها جابهجا نمیشوند: آرایهی «سوراخدار» میشود (اندیسها0 2 3 4).${#fruits[@]}تعداد عضوهای موجود را میدهد (۴) و${!fruits[@]}اندیسهای موجود را. اگر اندیسهای پیوسته میخواهی،arr=("${arr[@]}")دوباره بستهبندیشان میکند.- دور
unsetکوتیشن تکی گذاشتیم تا شل[1]را مثل الگوی نام فایل تفسیر نکند (در «اشتباهات رایج» میبینی چرا مهم است).
مثال ۳: کوتیشن، مهمترین قاعدهی آرایهها
Section titled “مثال ۳: کوتیشن، مهمترین قاعدهی آرایهها”مثل "$@" در درس آرگومانها: "${arr[@]}" هر عضو را یک آرگومان جدا میدهد، حتی اگر فاصله داشته باشد:
files=("my report.txt" "notes.txt" "old data.csv")
show() { local i=0 for a in "$@"; do echo " arg $((++i)): [$a]" done}
echo '--- show "${files[@]}" (درست):'show "${files[@]}"echo '--- show ${files[@]} (بدون کوتیشن: در فاصلهها میشکند):'show ${files[@]}echo '--- show "${files[*]}" (همه در یک رشته):'show "${files[*]}"echo '--- show $files (فقط عضو اول):'show $filesecho "--- چسباندن عضوها با یک جداکننده (IFS داخل پرانتز، تا IFS اصلی دست نخورد):"( IFS=,; echo "${files[*]}" )--- show "${files[@]}" (درست): arg 1: [my report.txt] arg 2: [notes.txt] arg 3: [old data.csv]--- show ${files[@]} (بدون کوتیشن: در فاصلهها میشکند): arg 1: [my] arg 2: [report.txt] arg 3: [notes.txt] arg 4: [old] arg 5: [data.csv]--- show "${files[*]}" (همه در یک رشته): arg 1: [my report.txt notes.txt old data.csv]--- show $files (فقط عضو اول): arg 1: [my] arg 2: [report.txt]--- چسباندن عضوها با یک جداکننده (IFS داخل پرانتز، تا IFS اصلی دست نخورد):my report.txt,notes.txt,old data.csvسه شکل و سه نتیجه: "${files[@]}" سه آرگومان سالم؛ بدون کوتیشن پنج تکه (دو تا از اسمها شکسته شدند)؛ "${files[*]}" همه یک رشتهی واحد. برای چسباندن اعضا با ویرگول (یا هر جداکننده) از "${arr[*]}" و IFS استفاده میشود: اولین کاراکتر IFS بین اعضا مینشیند. قاعده: همیشه "${arr[@]}".
مثال ۴: پیمایش و ساختن آرایه از فایل و دستور
Section titled “مثال ۴: پیمایش و ساختن آرایه از فایل و دستور”mkdir -p ~/bashlab/ar && cd ~/bashlab/artouch "my report.txt" notes.txt data.csvfiles=(*.txt)echo "txt files: ${#files[@]}"echo "--- پیمایش مقدارها:"for f in "${files[@]}"; do echo "file: [$f]"doneecho "--- پیمایش با اندیس:"for i in "${!files[@]}"; do echo "$i: ${files[$i]}"doneecho "--- C-style:"for ((i = 0; i < ${#files[@]}; i++)); do echo -n "${files[i]} | "doneechotxt files: 2--- پیمایش مقدارها:file: [my report.txt]file: [notes.txt]--- پیمایش با اندیس:0: my report.txt1: notes.txt--- C-style:my report.txt | notes.txt |files=(*.txt) الگو را خود شل باز میکند و نامهای دارای فاصله سالم میمانند. این روش درست ساختن آرایهی فایلهاست (برخلاف files=($(ls))). حالا از خروجی یک دستور و از متن:
cd ~/bashlab/arprintf 'one\ntwo words\nthree\n' > lines.txtecho "--- mapfile: هر خط یک عضو (-t یعنی newline آخر را بردار):"mapfile -t lines < lines.txtdeclare -p linesecho "--- روش اشتباه: arr=(\$(cat file)) در فاصلهها میشکند:"words=($(cat lines.txt))declare -p wordsecho "--- شکستن یک رشته با read -ra:"IFS=, read -ra parts <<< "10.0.0.1,10.0.0.2,10.0.0.3"declare -p partsecho "--- مرتبکردن (sort) و برگرداندن به آرایه:"mapfile -t sorted < <(printf '%s\n' pear apple fig | sort)declare -p sorted--- mapfile: هر خط یک عضو (-t یعنی newline آخر را بردار):declare -a lines=([0]="one" [1]="two words" [2]="three")--- روش اشتباه: arr=($(cat file)) در فاصلهها میشکند:declare -a words=([0]="one" [1]="two" [2]="words" [3]="three")--- شکستن یک رشته با read -ra:declare -a parts=([0]="10.0.0.1" [1]="10.0.0.2" [2]="10.0.0.3")--- مرتبکردن (sort) و برگرداندن به آرایه:declare -a sorted=([0]="apple" [1]="fig" [2]="pear")mapfile -t arr < فایل(هممعنی:readarray) هر خط فایل را یک عضو میکند. برای خروجی یک دستور:mapfile -t arr < <(دستور)(process substitution، درس حلقهها).arr=($(دستور))خروجی را در فاصلهها میشکند و (با الگوهای*) باز هم میکند؛two wordsشد دو عضو. فقط وقتی که مطمئنی هر عضو یک کلمهی ساده است.read -ra arr <<< "a,b,c"باIFS=,رشته را به اعضا میشکند (-aیعنی در آرایه بریز).- مرتبسازی:
sortرویprintf '%s\n' "${arr[@]}"و بعدmapfile.
مثال ۵: آرایهی انجمنی
Section titled “مثال ۵: آرایهی انجمنی”وقتی میخواهی با اسم به چیزی برسی، بهجای شماره، declare -A (حتماً با -A) مینویسی:
declare -A portsports[http]=80ports[https]=443ports[ssh]=22ports[postgres]=5432echo "ssh -> ${ports[ssh]}"echo "count: ${#ports[@]}"echo "--- کلیدها و مقدارها (ترتیب دلخواهِ Bash، نه ترتیب افزودن):"for k in "${!ports[@]}"; do echo "$k=${ports[$k]}"doneecho "--- مرتبشده بر اساس کلید:"for k in $(printf '%s\n' "${!ports[@]}" | sort); do echo "$k=${ports[$k]}"doneecho "--- وجود کلید (-v) و حذف:"[[ -v ports[ssh] ]] && echo "ssh is defined"[[ -v ports[ftp] ]] || echo "ftp is not defined"unset 'ports[ssh]'echo "after unset: ${!ports[@]}"echo "--- ساخت با مقدار اولیه:"declare -A colors=([red]="#ff0000" [green]="#00ff00")declare -p colorsssh -> 22count: 4--- کلیدها و مقدارها (ترتیب دلخواهِ Bash، نه ترتیب افزودن):postgres=5432ssh=22https=443http=80--- مرتبشده بر اساس کلید:http=80https=443postgres=5432ssh=22--- وجود کلید (-v) و حذف:ssh is definedftp is not definedafter unset: postgres https http--- ساخت با مقدار اولیه:declare -A colors=([red]="#ff0000" [green]="#00ff00" )"${!ports[@]}" با علامت ! همهی کلیدها را میدهد. ترتیب کلیدها همان ترتیب افزودن نیست؛ اگر ترتیب مشخص میخواهی کلیدها را sort کن. با [[ -v map[key] ]] وجود کلید را میسنجی (برخلاف [[ -n ${map[key]} ]] که کلیدی با مقدار خالی را «وجود ندارد» میبیند).
کاربرد اصلی: شمارش. هر بار که چیزی دیده شد، شمارندهی کلیدش را زیاد کن. اینجا درخواستهای یک لاگ دسترسی را بر اساس IP و کد وضعیت میشماریم:
cat > ~/bashlab/access.log <<'EOF'10.0.0.1 20010.0.0.2 40410.0.0.1 20010.0.0.3 50010.0.0.2 20010.0.0.1 404EOFdeclare -A hitsdeclare -A statuseswhile read -r ip status; do ((hits[$ip]++)) ((statuses[$status]++))done < ~/bashlab/access.logecho "--- requests per IP:"for ip in $(printf '%s\n' "${!hits[@]}" | sort); do echo "$ip ${hits[$ip]}"doneecho "--- requests per status code:"for s in $(printf '%s\n' "${!statuses[@]}" | sort); do echo "$s ${statuses[$s]}"done--- requests per IP:10.0.0.1 310.0.0.2 210.0.0.3 1--- requests per status code:200 3404 2500 1((hits[$ip]++)) اگر کلید هنوز نبود، آن را از صفر شروع میکند و یکی زیاد میکند؛ همین یک خط کل کار شمارش است. (همین کار با sort | uniq -c هم میشد؛ ولی وقتی چند چیز را همزمان میشماری یا نتیجه را در ادامهی اسکریپت به کار میبری، آرایهی انجمنی بهتر است.)
/bin/bash -c 'declare -A m' 2>&1/bin/bash -c 'a=(x y); echo "${a[-1]}"' 2>&1/bin/bash: line 0: declare: -A: invalid optiondeclare: usage: declare [-afFirtx] [-p] [name[=value] ...]/bin/bash: a: bad array subscriptآرایهی انجمنی در Bash ۴ به بعد هست. خروجی بالا از یک مک گرفته شده: bash ۳.۲ آن را نمیشناسد (declare: -A: invalid option) و اندیس منفی (${a[-1]}) هم ندارد. روی لینوکس مشکلی نیست؛ روی مک brew install bash بزن.
مثال ۶: آرایه و تابع
Section titled “مثال ۶: آرایه و تابع”آرایه را به تابع مثل آرگومانهای معمولی میدهی ("${arr[@]}") و تابع آنها را در "$@" میبیند. برگرداندن آرایه از تابع سختتر است؛ دو راه:
show() { echo "got $# arguments:" for a in "$@"; do echo " [$a]" done}
names=("Ali Ahmadi" "Sara" "Reza Karimi")show "${names[@]}"
echo "--- راه ۱: تابع چند خط چاپ کند، بیرون با mapfile بگیری:"list_dirs() { printf '%s\n' /etc /usr /var}mapfile -t dirs < <(list_dirs)declare -p dirs
echo "--- راه ۲: نام آرایهی بیرونی را بدهی (nameref، Bash 4.3+):"fill() { local -n out=$1 out=(x y z)}fill resultdeclare -p resultgot 3 arguments: [Ali Ahmadi] [Sara] [Reza Karimi]--- راه ۱: تابع چند خط چاپ کند، بیرون با mapfile بگیری:declare -a dirs=([0]="/etc" [1]="/usr" [2]="/var")--- راه ۲: نام آرایهی بیرونی را بدهی (nameref، Bash 4.3+):declare -a result=([0]="x" [1]="y" [2]="z")در راه اول داده با printf '%s\n' (هر عضو در یک خط) از تابع بیرون میآید و mapfile آن را به آرایه تبدیل میکند (همان مشکل «عضو با خط جدید» را دارد، ولی برای اکثر دادهها خوب است). در راه دوم local -n out=$1 یک اشارهگر به متغیری میسازد که اسمش را آرگومان دادهاند؛ هر چه به out بدهی، به result میرود. چون fill مستقیم (بدون $(...)) صدا زده شد، تغییر باقی ماند.
مثال ۷: حذف تکراریها با حفظ ترتیب
Section titled “مثال ۷: حذف تکراریها با حفظ ترتیب”آرایهی انجمنی بهعنوان «مجموعهی دیدهشدهها»: هر چیزی که قبلاً دیدهای را رد کن:
items=(b a c a b d a)declare -A seenunique=()for x in "${items[@]}"; do if [[ ! -v seen[$x] ]]; then seen[$x]=1 unique+=("$x") fidoneecho "input: ${items[@]}"echo "unique (in order): ${unique[@]}"mapfile -t sorted_unique < <(printf '%s\n' "${items[@]}" | sort -u)echo "sort -u (sorted): ${sorted_unique[@]}"input: b a c a b d aunique (in order): b a c dsort -u (sorted): a b c dحلقهی اول ترتیب اولین دیدهشدن را نگه میدارد (b a c d)، در حالی که sort -u مرتبشده میدهد (a b c d). هر کدام را بسته به نیازت انتخاب کن.
پشت پرده: آرایه چیست؟
Section titled “پشت پرده: آرایه چیست؟”آرایهی ساده در Bash در واقع یک نقشهی «اندیس ← مقدار» است، نه آرایهی پشتسرهم در حافظه. برای همین «سوراخدار» میشود و اندیسها لازم نیست پیوسته باشند. declare -p همین ساختار را نشان میدهد ([0]="apple" [2]="cherry pie"). چند نتیجهی عملی:
۱) آرایه به پروسههای فرزند نمیرسد. محیط (environment) فقط متن حمل میکند و export برای آرایه کار نمیکند:
rainbow=(red green blue)export rainbowbash -c 'echo "child sees: [${rainbow[0]}]"'child sees: []فرزند چیزی نمیبیند. اگر باید آرایه را به یک اسکریپت دیگر بدهی، بهصورت آرگومان ("${rainbow[@]}") یا خطبهخط در فایل بده.
۲) آرایه در sh (dash) نیست:
sh -c 'a=(1 2 3); echo "${a[0]}"' 2>&1sh: 1: Syntax error: "(" unexpectedآرایه مخصوص bash است. اگر اسکریپت باید با sh کار کند، همانطور که در درس اولین اسکریپت دیدی، باید از رشته و "$@" استفاده کنی.
۳) ویرگول جداکننده نیست. اعضا را فاصله جدا میکند:
wrong=(1,2,3)echo "count: ${#wrong[@]}"right=(1 2 3)echo "count: ${#right[@]}"count: 1count: 3جدولهای مرجع
Section titled “جدولهای مرجع”آرایهی ساده:
| عبارت | معنی |
|---|---|
arr=(a b "c d") |
ساخت (عضو دارای فاصله در کوتیشن) |
arr[2]=x |
تغییر یا ساخت یک عضو |
arr+=(x y) |
افزودن به انتها |
${arr[1]} / ${arr[-1]} |
یک عضو / آخرین عضو |
"${arr[@]}" |
همهی عضوها، هر کدام جدا |
"${arr[*]}" |
همهی عضوها در یک رشته (جداکنندهی IFS) |
${#arr[@]} |
تعداد عضوها |
"${!arr[@]}" |
اندیسهای موجود |
"${arr[@]:1:2}" |
برش: از اندیس ۱، دو عضو |
unset 'arr[1]' |
حذف یک عضو (سوراخ میماند) |
arr=("${arr[@]}") |
بستهبندی مجدد (اندیسها دوباره پیوسته) |
mapfile -t arr < file |
هر خط فایل یک عضو |
declare -p arr |
نمایش ساختار دقیق |
آرایهی انجمنی:
| عبارت | معنی |
|---|---|
declare -A map |
تعریف (بدون -A آرایهی ساده میشود!) |
map[key]=value |
مقداردهی |
declare -A m=([a]=1 [b]=2) |
تعریف با مقدار اولیه |
"${map[key]}" |
خواندن |
"${!map[@]}" / "${map[@]}" |
همهی کلیدها / همهی مقدارها |
[[ -v map[key] ]] |
کلید وجود دارد؟ |
unset 'map[key]' |
حذف کلید |
((count[$w]++)) |
شمارندهی هر کلید |
اشتباهات رایج
Section titled “اشتباهات رایج”۱) $arr بهجای "${arr[@]}"
Section titled “۱) $arr بهجای "${arr[@]}"”مثال ۱: echo $arr فقط عضو اول، ${#arr} طول عضو اول. راهحل: "${arr[@]}" و ${#arr[@]}.
۲) ${arr[@]} بدون کوتیشن
Section titled “۲) ${arr[@]} بدون کوتیشن”مثال ۳: عضوهای دارای فاصله میشکنند. راهحل: همیشه کوتیشن.
۳) آرایهی انجمنی بدون declare -A
Section titled “۳) آرایهی انجمنی بدون declare -A”n[apple]=1n[banana]=2declare -p ndeclare -a n=([0]="2")بدون declare -A، Bash n را آرایهی ساده میگیرد و apple و banana را مثل عبارت حسابی میسنجد (متغیرهای تعریفنشده، صفر)، پس هر دو در اندیس ۰ نوشته میشوند و فقط آخری میماند: [0]="2". بیصدا و غلط. راهحل: همیشه اول declare -A n.
۴) arr=($(دستور)) برای خروجیهای دارای فاصله
Section titled “۴) arr=($(دستور)) برای خروجیهای دارای فاصله”مثال ۴: two words دو عضو شد. راهحل: mapfile -t arr < <(دستور).
۵) unset arr[1] بدون کوتیشن
Section titled “۵) unset arr[1] بدون کوتیشن”cd ~/bashlabletters=(a b c)touch arr1unset arr[1]echo "after unset arr[1] : ${letters[@]}"unset 'letters[1]'echo "after unset 'letters[1]' : ${letters[@]}"rm arr1after unset arr[1] : a b cafter unset 'letters[1]' : a cشل arr[1] را الگوی نام فایل حساب میکند. چون فایلی به اسم arr1 بود، دستور شد unset arr1 و آرایه دستنخورده ماند (و letters اصلاً ربطی نداشت). با کوتیشن تکی درست حذف شد. راهحل: همیشه unset 'arr[i]'.
۶) ویرگول بهجای فاصله
Section titled “۶) ویرگول بهجای فاصله”پشت پرده: (1,2,3) یک عضو است.
۷) آرایه در sh یا macOS قدیمی
Section titled “۷) آرایه در sh یا macOS قدیمی”پشت پرده و مثال ۵: Syntax error: "(" unexpected در sh و نبودن آرایهی انجمنی در bash ۳.۲.
آرایهی shades را با چهار رنگ بساز (یکی دو کلمهای: "light blue"). تعداد، عضو اول، عضو آخر و همهی رنگها را هر کدام در یک خط شمارهدار (1. red) چاپ کن.
دیدن جواب
shades=(red green "light blue" yellow)echo "count: ${#shades[@]}"echo "first: ${shades[0]}"echo "last: ${shades[-1]}"position=1for c in "${shades[@]}"; do echo "$position. $c" ((position++))donecount: 4first: redlast: yellow1. red2. green3. light blue4. yellowتمرین اصلی درس: لیست سرورها را در آرایه بگذار و به هر کدام ping بزن. اسکریپت ping-servers.sh برای هر سرور بنویسد UP یا DOWN، در آخر خلاصه (چند سرور، چند بالا، چند پایین) چاپ کند، و فقط وقتی همه بالا بودند با کد ۰ تمام شود. (برای نتیجهی قابلتکرار: 127.0.0.1 و localhost همیشه بالا هستند؛ 192.0.2.1 از بازهی مخصوص مستندات است و جواب نمیدهد؛ و دامنهی nonexistent.invalid هرگز وجود ندارد.)
دیدن جواب
cd ~/bashlabcat > ping-servers.sh <<'EOF'#!/usr/bin/env bash# ping-servers.sh: ping every server in a list and reportservers=(127.0.0.1 localhost 192.0.2.1 nonexistent.invalid)up=0down=0
for server in "${servers[@]}"; do if ping -c 1 -W 1 "$server" > /dev/null 2>&1; then echo "UP $server" ((up++)) else echo "DOWN $server" ((down++)) fidone
echo "Summary: ${#servers[@]} servers, $up up, $down down"(( down == 0 ))EOFchmod +x ping-servers.sh./ping-servers.shecho "کد خروج: $?"UP 127.0.0.1UP localhostDOWN 192.0.2.1DOWN nonexistent.invalidSummary: 4 servers, 2 up, 2 downکد خروج: 1ping -c 1 -W 1 فقط یک بسته میفرستد و حداکثر یک ثانیه صبر میکند؛ > /dev/null 2>&1 خروجی را دور میریزد و فقط کد خروج میماند (۰ یعنی جواب گرفت). آخرین خط (( down == 0 )) خودش کد خروج اسکریپت میشود: ۰ اگر هیچ سروری پایین نبود. برای اضافهکردن سرور فقط یک کلمه به آرایه میافزایی.
اسکریپت top-words.sh که یک متن (با echo/فایل) بگیرد و تعداد تکرار هر کلمه را بشمارد (حروف کوچک و بزرگ یکی)، و سه کلمهی پرتکرار را با تعدادشان چاپ کند. از آرایهی انجمنی استفاده کن.
دیدن جواب
cd ~/bashlabcat > top-words.sh <<'EOF'#!/usr/bin/env bash# top-words.sh: count word frequencies (case-insensitive) and print the top 3declare -A countwhile read -r word; do [[ -z $word ]] && continue ((count[${word,,}]++))done < <(tr -s '[:space:][:punct:]' '\n' < "${1:-/dev/stdin}")
for w in "${!count[@]}"; do echo "${count[$w]} $w"done | sort -rn | head -n 3EOFchmod +x top-words.shecho "the cat and The dog and THE bird saw a cat" > text.txt./top-words.sh text.txt3 the2 cat2 andنکتهها: ${word,,} کلمه را به حروف کوچک تبدیل میکند (درس بعد)؛ tr -s متن را کلمهبهکلمه (هر کلمه در یک خط) میکند؛ for کلیدها را چاپ میکند و sort -rn | head -n 3 سه تای اول را از نظر تعداد برمیدارد. (برای تساوی تعداد، ترتیب اسمها بسته به sort است.)
یک جدول دستور (dispatch table) بساز: آرایهی انجمنی tasks که اسم کار ← اسم تابع را نگه دارد (date ← show_date، disk ← show_disk، user ← show_user). اسکریپت tasks.sh اسم کار را از آرگومان بگیرد و تابع مربوطه را اجرا کند؛ اگر اسم ناشناخته یا خالی بود، فهرست کارها را (مرتبشده) در راهنما چاپ کند و با کد ۱ تمام شود.
دیدن جواب
cd ~/bashlabcat > tasks.sh <<'EOF'#!/usr/bin/env bash# tasks.sh: a dispatch table (associative array: task name -> function name)
show_date() { date +%F; }show_disk() { df -h / | tail -n 1; }show_user() { whoami; }
declare -A tasks=( [date]=show_date [disk]=show_disk [user]=show_user)
name="$1"if [[ -z $name || ! -v tasks[$name] ]]; then names=$(printf '%s\n' "${!tasks[@]}" | sort | paste -sd'|') echo "Usage: $0 {$names}" >&2 exit 1fi
"${tasks[$name]}"EOFchmod +x tasks.sh./tasks.sh user./tasks.sh date./tasks.sh disk./tasks.sh nothing; echo "کد خروج: $?"./tasks.sh; echo "کد خروج: $?"ali2026-10-04overlay 453G 23G 408G 6% /Usage: ./tasks.sh {date|disk|user}کد خروج: 1Usage: ./tasks.sh {date|disk|user}کد خروج: 1مزیت این الگو: اضافهکردن یک کار جدید یعنی یک تابع و یک خط در جدول؛ بدون case طولانی. و چون فقط اسمهای موجود در جدول اجرا میشوند، ورودی کاربر هرگز مستقیم بهعنوان دستور اجرا نمیشود (برخلاف eval) که امنتر است.
آزمونک
Section titled “آزمونک”آرایهای سهعضوی دارید: apple و «cherry pie» (دو کلمه) و date. دستورهای echo ${#arr[@]} و echo ${#arr[1]} چه چاپ میکنند؟
${#arr[@]} تعداد عضوهاست (۳) و ${#arr[1]} طول متن عضو دوم، یعنی «cherry pie» که ۱۰ کاراکتر است.
echo $arr وقتی arr=(a b c) است چه چاپ میکند؟
$arr یعنی ${arr[0]}. برای همهی عضوها "${arr[@]}".
چرا باید "${files[@]}" را با کوتیشن نوشت؟
درست مثل "$@" در آرگومانهای اسکریپت.
بعد از unset 'arr[1]' روی آرایهی سهعضوی، ${!arr[@]} چه نشان میدهد؟
اندیسها جابهجا نمیشوند؛ arr=("${arr[@]}") دوباره پیوستهشان میکند.
اگر declare -A را فراموش کنی و بنویسی m[apple]=1; m[banana]=2 چه میشود؟
بیصدا غلط است؛ همیشه declare -A m.
کدام روش برای ساختن آرایهای از خطهای یک فایل (حتی خطهای دارای فاصله) درست است؟
روش اول در فاصلهها میشکند.
export arr (که آرایه است) و بعد bash -c 'echo ${arr[0]}' چه نشان میدهد؟
محیط فقط متن حمل میکند؛ آرایه را بهصورت آرگومان بده.
جمعبندی
Section titled “جمعبندی”- آرایهی ساده:
arr=(a b "c d")؛ خواندن${arr[i]}، همه با"${arr[@]}"(هر عضو یک آرگومان)، تعداد${#arr[@]}، اندیسها"${!arr[@]}"، افزودن+=(x)، حذفunset 'arr[i]'، برش${arr[@]:1:2}. - همیشه کوتیشن:
"${arr[@]}". بدون اندیس،$arrفقط عضو اول است و${#arr}طول عضو اول. - ساختن از فایل و دستور:
files=(*.txt)(امن برای فاصله)،mapfile -t arr < file،read -ra arr <<< "a,b". اجتناب ازarr=($(cmd)). - آرایهی انجمنی:
declare -A map(حتماً)،map[key]=v، کلیدها"${!map[@]}"(بدون ترتیب)،[[ -v map[key] ]]، شمارنده((map[$k]++))، جدول دستور (name ← function). declare -pساختار دقیق آرایه را نشان میدهد؛ برای دیباگ بهترین ابزار است.- آرایه به فرزندها نمیرسد (
exportکار نمیکند)، درshنیست، و آرایهی انجمنی bash ۴+ میخواهد (مک: ۳.۲). - وقتی آرایه را به تابع میدهی،
"${arr[@]}"؛ برای برگرداندن،mapfileروی خروجی یاlocal -n.
| دستور | کاری که میکند |
|---|---|
arr=(a b "c d") arr+=(e) | ساخت / افزودن |
"${arr[@]}" ${arr[0]} ${arr[-1]} | همه (جدا جدا) / یک عضو / آخرین |
${#arr[@]} "${!arr[@]}" | تعداد / اندیسها |
for x in "${arr[@]}"; do ...; done | پیمایش مقدارها |
for i in "${!arr[@]}"; do ...; done | پیمایش با اندیس |
unset 'arr[1]' arr=("${arr[@]}") | حذف (سوراخ) / فشردهسازی مجدد |
files=(*.txt) | آرایه از فایلها (امن برای فاصله) |
mapfile -t lines < file | هر خط فایل یک عضو |
IFS=, read -ra parts <<< "a,b,c" | شکستن رشته به آرایه |
declare -A map; map[k]=v | آرایهی انجمنی |
"${!map[@]}" [[ -v map[k] ]] ((map[$k]++)) | کلیدها / وجود کلید / شمارش |
declare -p arr | دیدن ساختار دقیق آرایه |