حلقهها یکی از بنیادیترین ابزارهای هر زبان اسکریپتنویسی هستند. در Bash، با چهار ساختار اصلی برای تکرار مواجه هستیم: for، while، until، و دو دستور کنترل جریان break و continue که به شما امکان مدیریت دقیقتری بر روند اجرای حلقه میدهند. در این درس، هر یک را با جزئیات کامل، مثالهای عملی و نکات ظریف بررسی میکنیم.
حلقهی for
حلقهی for در Bash دو شکل سینتکسی کاملاً متفاوت دارد: سبک لیستی (برای iterating بر روی یک لیست از آیتمها) و سبک C (برای شمارش و تکرار با شمارنده).
۱. سبک لیستی (List-style)
این شکل، آیتمهای یک لیست را یکییکی پیمایش میکند. لیست میتواند توسط فاصله (یا بهطور دقیقتر، توسط IFS) از هم جدا شده باشد.
for variable in list; do commands done
مثالهای عملی:
#!/bin/bash # پیمایش یک لیست ثابت for fruit in سیب موز پرتقال; do echo "میوه: $fruit" done
#!/bin/bash # استفاده از brace expansion for i in {1..5}; do echo "شماره: $i" done
#!/bin/bash # پیمایش خروجی یک دستور for file in $(ls *.txt); do echo "پردازش فایل: $file" wc -l "$file" done
#!/bin/bash # پیمایش آرگومانهای اسکریپت (استفاده از "$@") for arg in "$@"; do echo "آرگومان دریافتی: $arg" done
نکتهی امنیتی: در مثال
$(ls *.txt)، اگر نام فایلها شامل فاصله باشد، مشکل ایجاد میشود (تقسیم کلمه اتفاق میافتد). برای پیمایش امن فایلها، از حلقهی مستقیم روی wildcard استفاده کنید:for file in *.txt; do echo "$file" doneاین روش، نام فایلهای دارای فاصله را بهدرستی حفظ میکند.
۲. سبک C (C-style)
این شکل برای زمانی که به یک شمارندهی عددی با شروع، شرط پایان و گام (step) نیاز دارید، ایدهآل است.
for (( expr1; expr2; expr3 )); do commands done
-
expr1: مقداردهی اولیه (معمولاًi=0) -
expr2: شرط ادامه (تا زمانی که درست باشد، حلقه ادامه مییابد) -
expr3: عملیات انتهای هر تکرار (معمولاًi++یاi+=2)
#!/bin/bash # حلقه از ۱ تا ۱۰ for (( i=1; i<=10; i++ )); do echo "تکرار $i" done
#!/bin/bash # حلقه با گام ۲ (اعداد فرد) for (( i=1; i<=10; i+=2 )); do echo "عدد فرد: $i" done
#!/bin/bash # حلقه نزولی for (( i=10; i>=1; i-- )); do echo "شمارش معکوس: $i" done
سبک C درون
(( ))از متغیرها بدون علامت$استفاده میکند (i=0نه$i=0). این ویژگی آن را بهشدت خواناتر میکند.
حلقهی while
حلقهی while تا زمانی که شرط آن درست (کد خروج 0) باشد، به اجرای بدنه ادامه میدهد.
while condition; do commands done
مثالهای عملی:
۱. شمارنده:
#!/bin/bash counter=1 while [[ $counter -le 5 ]]; do echo "شمارنده: $counter" (( counter++ )) done
۲. حلقهی بینهایت (با true):
#!/bin/bash while true; do read -p "یک دستور وارد کنید (یا 'exit' برای خروج): " cmd if [[ "$cmd" == "exit" ]]; then break fi eval "$cmd" done
۳. خواندن فایل خطبهخط (کاربرد بسیار رایج):
#!/bin/bash # روش استاندارد و امن برای خواندن فایلها while IFS= read -r line; do echo "خط: $line" done < "/etc/passwd"
-
IFS=باعث میشود فضاهای ابتدا و انتهای خط حذف نشوند. -
-rاز تفسیر بکاسلشها جلوگیری میکند. -
< "/etc/passwd"ورودی حلقه را از فایل تأمین میکند (نه از stdin کاربر).
۴. خواندن خروجی یک دستور خطبهخط (با process substitution):
#!/bin/bash while IFS= read -r line; do echo "پردازش: $line" done < <(ps aux | grep bash)
حلقهی until
حلقهی until دقیقاً برعکس while عمل میکند: تا زمانی که شرط آن نادرست (کد خروج غیرصفر) باشد، به اجرا ادامه میدهد. به محض درستشدن شرط، حلقه خاتمه مییابد.
until condition; do commands done
مثالهای عملی:
۱. شمارنده (معکوس while):
#!/bin/bash counter=1 until [[ $counter -gt 5 ]]; do echo "شمارنده: $counter" (( counter++ )) done
(توجه کنید که شرط خاتمه، -gt 5 است؛ یعنی تا زمانی که شمارنده از ۵ بزرگتر نشده، اجرا کن.)
۲. انتظار برای برقراری شرط (مثلاً منتظر بودن برای ایجاد یک فایل):
#!/bin/bash # تا زمانی که فایل lock.txt وجود ندارد، صبر کن until [[ -f "/tmp/lock.txt" ]]; do echo "منتظر ایجاد فایل lock.txt..." sleep 2 done echo "فایل lock.txt ایجاد شد. ادامهی اسکریپت..."
۳. انتظار برای پاسخ از یک سرویس (چک کردن پورت):
#!/bin/bash until nc -z localhost 8080; do echo "منتظر شروع سرویس روی پورت 8080..." sleep 1 done echo "سرویس آماده است!"
دستورات break و continue
این دو دستور، کنترل جریان اجرای حلقه را در اختیار شما قرار میدهند.
break — خروج از حلقه
دستور break باعث خروج فوری از حلقهای میشود که درون آن قرار دارد. اگر حلقهها تو در تو باشند، میتوان با break N از N سطح حلقه خارج شد.
#!/bin/bash # خروج از حلقه در صورت بروز شرط خاص for i in {1..10}; do if [[ $i -eq 5 ]]; then echo "رسیدیم به ۵، خروج!" break fi echo "عدد: $i" done # خروجی: 1,2,3,4, "رسیدیم به ۵، خروج!"
خروج از حلقههای تو در تو:
#!/bin/bash # حلقهی تو در تو با break 2 for i in {1..3}; do for j in {1..3}; do if [[ $i -eq 2 && $j -eq 2 ]]; then echo "خروج از هر دو حلقه!" break 2 # خروج از حلقهی i و j fi echo "i=$i, j=$j" done done # خروجی: (1,1) (1,2) (1,3) (2,1) و سپس "خروج از هر دو حلقه!" و پایان کار
اگر
breakبدون عدد داده شود، معادلbreak 1است و فقط از یک سطح خارج میشود.
continue — پرش به تکرار بعدی
دستور continue اجرای دستورات باقیمانده در بدنهی حلقه را برای تکرار جاری متوقف کرده و به تکرار بعدی (آزمون شرط حلقه) میپرد.
#!/bin/bash # چاپ اعداد فرد (پرش از اعداد زوج) for i in {1..10}; do if (( i % 2 == 0 )); then continue fi echo "عدد فرد: $i" done # خروجی: 1,3,5,7,9
استفاده در حلقههای تو در تو:
continue N باعث میشود که N-امین حلقهی بیرونی، تکرار بعدی خود را آغاز کند (دستورات باقیمانده در آن سطح و تمام حلقههای داخلیتر نادیده گرفته میشوند).
#!/bin/bash for i in {1..3}; do echo "حلقه بیرونی i=$i" for j in {1..3}; do if [[ $j -eq 2 ]]; then continue 2 # به تکرار بعدی i میپرد fi echo " داخلی j=$j" done done
جمعبندی مقایسهای
| حلقه | شرط اجرا | کاربرد اصلی |
|---|---|---|
for (list) | پیمایش آیتمهای یک لیست | پردازش فایلها، آرگومانها، یا خروجی دستورات |
for ((C)) | شمارنده با شروع، شرط پایان و گام | شمارش دقیق، کار با اندیسها، ماتریسها |
while | تا زمانی که شرط درست است | حلقههای بینهایت، خواندن فایل، منتظر ماندن برای شرط فعال |
until | تا زمانی که شرط نادرست است | منتظر ماندن برای درستشدن یک شرط (معکوس while) |
break | خروج فوری از حلقه | پایان دادن به حلقه بر اساس شرط خاص |
continue | پرش به تکرار بعدی | نادیدهگرفتن بخشی از بدنه برای برخی موارد |
نکات پیشرفته و تلههای رایج
تلهی ۱: تغییر
IFSدر حلقهها
اگر در حلقهیforدر حال پیمایش یک رشته با جداکنندهی خاص هستید، تغییرIFSمیتواند مفید باشد. اما فراموش نکنید کهIFSرا به حالت اولیه برگردانید تا سایر بخشهای اسکریپت تحت تأثیر قرار نگیرند.OLD_IFS="$IFS" IFS=":" for field in $(cat /etc/passwd); do echo "$field" done IFS="$OLD_IFS"
تلهی ۲: حلقههای
whileدرون پایپ (pipe)
وقتی خروجی یک دستور را بهwhileپایپ میکنید (command | while read line; do ...)، بدنهی حلقه در یک زیر-شل (subshell) اجرا میشود. این یعنی تغییرات متغیرها در بدنهی حلقه، پس از اتمام حلقه، در شل اصلی دیده نمیشوند.# مشکلدار count=0 cat file.txt | while read line; do (( count++ )) done echo "تعداد خطوط: $count" # خروجی: 0 (چون count در subshell تغییر کرده) # راهحل: استفاده از redirection بهجای pipe count=0 while read line; do (( count++ )) done < file.txt echo "تعداد خطوط: $count" # خروجی درست
تلهی ۳: حلقههای بینهایت ناخواسته
اگر در حلقهیwhileیاuntilشرطی بنویسید که هرگز تغییر نمیکند، اسکریپت قفل میشود. برای مثال:x=5 while [[ $x -gt 0 ]]; do echo "x=$x" # فراموش کردن decrement doneهمیشه مطمئن شوید که شرط حلقه در بدنه تغییر میکند یا از
breakبرای خروج استفاده میکنید.
نکتهی حرفهای:
read -tدر حلقهها
در اسکریپتهای تعاملی که منتظر ورودی کاربر هستند، میتوان ازread -tباwhileبرای ایجاد تایماوت استفاده کرد:while true; do if read -t 5 -p "ورودی را وارد کنید (۵ ثانیه فرصت): " input; then echo "دریافت شد: $input" else echo "زمان تمام شد!" break fi done