یک حلقه، قطعه کدی است که مشروط به صحیح بودن شرط کنترل حلقه، لیستی از فرمانها را تکرار میکند. [1]
این ساختار اصلی ایجاد حلقه است. به طور قابل توجهی با شکل همتایش در C تفاوت دارد.
for arg in [list]
do
command(s)...
done
در هر نوبت عبور از میان حلقه، arg مقدار یکی از متغیرهای متوالی در list را میگیرد. |
for arg in "$var1" "$var2" "$var3" ... "$varN" #در دور اول حلقه، arg = $var1 #در دور دوم حلقه، arg = $var2 #در دور سوم حلقه، arg = $var3 # ... #در نوبت Nام حلقه، arg = $varN #برای پیشگیری از تفکیک کلمه احتمالی، شناسهها در [list] نقلقولی شدهاند.
شناسه list میتواند شامل کاراکترهای عام باشد.
اگر do در همان سطر for باشد، وجود یک سمیکالن بعد از list لازم است.
for arg in [list] ; do
مثال 11-1. حلقههای ساده for
#!/bin/bash # for planet in Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto do echo $planet # done echo; echo for planet in "Mercury Venus Earth Mars Jupiter Saturn Uranus Neptune Pluto" # #کل list محصور شده در نقلقولها، یک متغیر منفرد ایجاد میکند. # do echo $planet done echo; echo "Whoops! Pluto is no longer a planet!" exit 0
هر عضو [list] میتواند شامل پارامترهای متعددی باشد . این مطلب موقع پردازش پارامترهای گروهی مفید است. در چنین مواردی، برای تجزیه اجباری هر عضو [list] و تخصیص هر جزء تشکیلدهنده به پارامترهای مکانی، فرمان set را به کار ببرید ( مثال 15-16 را ببینید).
مثال 11-2. حلقه for با دو پارامتر در هرعضوِِ [list]
#!/bin/bash # # for planet in "Mercury 36" "Venus 67" "Earth 93" "Mars 142" "Jupiter 483" do set -- $planet #متغیر "planet" را تجزیه نموده، و #+ #کاربرد "--" در صورتیکه $planet تهی باشد یا با یک خط تیره #+ # #+ #آرایه است، به این شکل: original_params=("$@") echo "$1 $2,000,000 miles from the sun" #پیوست نمودن صفرها با پارامتر $2 |-دو تا tab -| done #(با تشکر از S.C. به خاطر شفافسازی بیشتر.) exit 0
در یک حلقه for ممکن است یک متغیر [list] را تامین نماید.
مثال 11-3. Fileinfo: عمل کردن روی فهرستی از فایلها که محتوای یک متغیر هستند
#!/bin/bash # FILES="/usr/sbin/accept /usr/sbin/pwck /usr/sbin/chroot /usr/bin/fakefile /sbin/badblocks /sbin/ypbind" # #قرار دادن یک فایل ساختگی /usr/bin/fakefile بین آنها. echo for file in $FILES do if [ ! -e "$file" ] # then echo "$file does not exist."; echo continue # fi ls -l $file | awk '{ print $8 " file size: " $5 }' # whatis `basename $file` # #توجه نمایید که برای این منظور لازم است بانک اطلاعات whatis آماده شده باشد. #برای انجام این کار، به عنوان کاربر ارشد /usr/bin/makewhatis را اجرا کنید. echo done exit 0
در یک حلقه for ممکن است [list] پارامتری بشود.
مثال 11-4. عمل نمودن روی یک لیست فایل پارامتری شده
#!/bin/bash filename="*txt" for file in $filename do echo "Contents of $file" echo "---" cat "$file" echo done
در صورتیکه [list] در یک حلقه for شامل کاراکترهای عام (* و ?) مورد استفاده در بسط نام فایل باشد، آنوقت globbing صورت میگیرد.
مثال 11-5. عمل کردن روی فایلها با یک حلقه for
#!/bin/bash #اسکریپت list-glob.sh: تولید [list] در یک حلقه for با کاربرد globbing ... # echo for file in * # #+ do ls -l "$file" #تمام فایلها در $PWD (دایرکتوری جاری) را لیست میکند. #به یاد بیاورید که کاراکتر "*" با هر نام فایلی مطابقت میکند، #+اما در "globbing" با فایلهای نقطهای منطبق نمیشود. # #برای پرهیز از این مورد، گزینه nullglob را تنظیم کنید. #+به این شکل: shopt -s nullglob #با تشکر از S.C. done echo; echo for file in [jx]* do rm -f $file #فقط فایلهایی در $PWD را که با j یا x شروع شوند حذف میکند. echo "Removed file \"$file\"". done echo exit 0
از قلم انداختن بخش in [list] از یک حلقه for باعث میشود حلقه روی $@ یعنی پارامترهای مکانی عمل نماید. یک توضیح ماهرانه مشروحِ این مطلب، مثال A-15 است. همچنین مثال 15-17 را ملاحظه نمایید.
مثال 11-6. فقدان in [list] در یک حلقه for
#!/bin/bash # for a do echo -n "$a " done #'in list' غایب است، بنابراین حلقه روی '$@' عمل میکند #+(لیست شناسههای خط فرمان، شامل فضای سفید). echo exit 0
استفاده از جایگزینی فرمان برای تولید [list] در یک حلقه for امکانپذیر است. همچنین مثال 16-54، مثال 11-11 و مثال 16-48 را ببینید.
مثال 11-7. تولید [list] در یک حلقه for با جایگزینی فرمان
#!/bin/bash #اسکریپت for-loopcmd.sh: حلقه for با [list] تولید شده توسط جایگزینی فرمان NUMBERS="9 7 3 8 37.53" for number in `echo $NUMBERS` #برای number در لیست 9 7 3 8 37.53 do echo -n "$number " done echo exit 0
این هم یک مثال تا اندازهای پیچیدهترِ استفاده از جایگزینی فرمان برای تولید [list].
مثال 11-8. یک جایگزینی grep برای فایلهای باینری
#!/bin/bash #اسکریپت bin-grep.sh: رشتههای منطبق را در یک فایل باینری جایگزین میکند. #یک جایگزینی grep برای فایلهای باینری. #مشابه اثر فرمان "grep -a" E_BADARGS=65 E_NOFILE=66 if [ $# -ne 2 ] then echo "Usage: `basename $0` search_string filename" exit $E_BADARGS fi if [ ! -f "$2" ] then echo "File \"$2\" does not exist." exit $E_NOFILE fi IFS=$'\012' #طبق پیشنهاد Anton Filippov #عبارت بود از: IFS="\n" for word in $( strings "$2" | grep "$1" ) #فرمان strings رشتهها در فایل باینری را لیست میکند. سپس خروجی #به grep لولهکشی میشود، که برای رشته مورد نظر معاینهاش میکند. do echo $word done #همچنانکه S.C. اشاره میکند، سطرهای 23 - 30 میتوانست با شکل سادهتر #strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]' تعویض بشود. #برای به کار بردن این اسکریپت موردی مانند #+"./bin-grep.sh mem /bin/ls" را امتحان کنید exit 0
بیشتر در همین مورد.
مثال 11-9. لیست کردن تمام کاربران سیستم
#!/bin/bash # PASSWORD_FILE=/etc/passwd n=1 # for name in $(awk 'BEGIN{FS=":"}{print $1}' < "$PASSWORD_FILE" ) #: جداکننده فیلد است ^^^^^^ #چاپ فیلد اول ^^^^^^^^ # do echo "USER #$n = $name" let "n += 1" done # # # # ... # exit $? # # ---------- #چطور است که یک کاربر معمولی، یا اسکریپت اجرا شده توسط او میتواند فایل #+/etc/passwd را بخواند؟ (اشاره: مجوزهای فایل /etc/passwd را بررسی کنید.) #آیا این یک حفره امنیتی است؟ چرا هست یا چرا نیست؟
بازهم یک مثال دیگر از [list] حاصل از جایگزینی فرمان.
مثال 11-10. کنترل تمام فایلهای باینری در یک دایرکتوری برای تعیین هویت نویسنده
#!/bin/bash # # directory=/usr/bin/ fstring="Free Software Foundation" # for file in $( find $directory -type f -name '*' | sort ) do strings -f $file | grep "$fstring" | sed -e "s%$directory%%" #لازم است که در عبارت sed جداکنندههای / معمولی تعویض بشوند، #+زیرا / یکی از کاراکترهایی است که فیلتر میگردد، اگر این #+ done exit $? #تمرین (آسان): # --------------- #اسکریپت را جهت قبول پارامترهای خط فرمان برای $directory و $fstring تغییر بدهید.
آخرین مثال از [list] و جایگزینی فرمان، اما این دفعه «فرمان» یک تابع است.
generate_list () { echo "one two three" } for word in $(generate_list) #برای آنکه word خروجی تابع را اخذ کند. do echo "$word" done # # #
خروجی یک حلقه for ممکن است به فرمان یا فرمانهایی لولهکشی بشود.
مثال 11-11. لیست نمودن پیوندهای نمادین در یک دایرکتوری
#!/bin/bash #symlinks.sh: پیوندهای نمادین در یک دایرکتوری را لیست میکند. directory=${1-`pwd`} # # # # ARGS=1 # # # if [ $# -ne "$ARGS" ] # # then # directory=`pwd` # # else # directory=$1 # fi # echo "symbolic links in directory \"$directory\"" for file in "$( find $directory -type l )" #-type l یعنی پیوندهای نمادین. do echo "$file" done | sort # # #+چون، خروجی فرمان find به یک کلمه منفرد بسط مییابد. # #به طوری که Dominik 'Aeneas' Schnitzer اشاره میکند، با #+کوتاهی در نقلقول نمودن $( find $directory -type l ) #+ exit 0 # -------------------------------------------------------- #Jean Helou جایگزینی مورد زیر را پیشنهاد میدهد: echo "symbolic links in directory \"$directory\"" #تهیه پشتیبان از IFS فعلی. شخص نمیتواند همیشه خیلی هشیار باشد. OLDIFS=$IFS IFS=: for file in $(find $directory -type l -printf "%p$IFS") do # echo "$file" done|sort #و James "Mike" Conley ویرایش کد Helou را بدین طریق پیشنهاد میکند: OLDIFS=$IFS IFS='' #IFS تهی یعنی کلمه خُرد نمیشود for file in $( find $directory -type l ) do echo $file done | sort # # #
همان طور که بهسازی جزئی زیر برای مثال قبل نشان میدهد، stdout یک حلقه میتواند به یک فایل تغییر مسیر داده شود.
مثال 11-12. پیوندهای نمادین در یک دایرکتوری، ذخیره شده در یک فایل
#!/bin/bash #symlinks.sh: پیوندهای نمادین در یک دایرکتوری را لیست میکند. OUTFILE=symlinks.list # directory=${1-`pwd`} # echo "symbolic links in directory \"$directory\"" > "$OUTFILE" echo "---------------------------" >> "$OUTFILE" for file in "$( find $directory -type l )" #-type l یعنی پیوندهای نمادین do echo "$file" done | sort >> "$OUTFILE" # # # exit $?
یک ترکیب دستوری جایگزین برای حلقه for وجود دارد که برای برنامهنویسان C خیلی آشنا است. این ترکیب به پرانتزهای دوگانه نیاز دارد.
مثال 11-13. یک حلقه for سبکِ C
#!/bin/bash #چندین روش برای شمارش تا 10 echo # for a in 1 2 3 4 5 6 7 8 9 10 do echo -n "$a " done echo; echo # #استفاده از "seq" ... for a in `seq 10` do echo -n "$a " done echo; echo # # #در Bash نگارش 3+. for a in {1..10} do echo -n "$a " done echo; echo # #اکنون بیایید همان کار را باترکیب C-مانند انجام بدهیم. LIMIT=10 for ((a=1; a <= LIMIT ; a++)) #پرانتزهای دوگانه و LIMIT بدون $ do echo -n "$a " done #یک ساختار اقتباس شده از ksh93. echo; echo # #بیایید از عملگر کامای C برای افزایش دو متغیر به طور همزمان، استفاده نماییم. for ((a=1, b=1; a <= LIMIT ; a++, b++)) do # echo -n "$a-$b " done echo; echo exit 0
همچنین مثال 27-16، مثال 27-17، و مثال A-6 را ببینید.
---
اکنون، یک حلقه for مورد استفاده در یک مضمون «واقعی».
مثال 11-14. کاربرد efax در وضعیت دستهای
#!/bin/bash #Fax کردن (باید بسته efax نصب شده باشد). EXPECTED_ARGS=2 E_BADARGS=85 MODEM_PORT="/dev/ttyS2" # #پورت پیشفرض کارت مودم PCMCIA if [ $# -ne $EXPECTED_ARGS ] # then echo "Usage: `basename $0` phone# text-file" exit $E_BADARGS fi if [ ! -f "$2" ] then echo "File $2 is not a text file." # exit $E_BADARGS fi fax make $2 #ایجاد فایلهای قالببندی شده fax از فایلهای متن. for file in $(ls $2.0*) #به هم پیوستن فایلهای تبدیل شده. #در لیست متغیر از کاراکتر عام («جانشینی» نام فایل) استفاده میکند. do fil="$fil $file" done efax -d "$MODEM_PORT" -t "T$1" $fil #عاقبت، انجام کار. #آزمایش افزودن -o1 در صورتی که سطر فوق ناموفق گردد. #به طوریکه S.C. اشاره میکند، حلقه for میتواند با استفاده #از efax -d /dev/ttyS2 -o1 -t "T$1" $2.0* حذف بشود. #+اما واقعاً آموزنده نیست. exit $? #efax پیغامهای تشخیصی نیز به خروجی استاندارد ارسال میکند.
کلیدواژههای do و done بلوک فرمانِ حلقه for را مشخص میکنند. اگرچه، ممکن است در برخی مضمونها، به واسطه قاببندی قطعهفرمان در داخل ابروها، این کلمهها ذکر نگردند.
for((n=1; n<=10; n++)) # |
این ساختار، یک شرط را در ابتدای یک حلقه بررسی میکند، و تا وقتی که آن شرط صحیح باشد (یک وضعیت خروج 0 برگشت بدهد) حلقه را حفظ میکند. در مقایسه با حلقه for، یک حلقه while در وضعیتهایی که تعداد تکرار از قبل شناخته شده نیست مورد استفاده مییابد.
while [ condition ]
do
command(s)...
done
ساختار براکت در یک حلقه while چیزی غیراز دوست قدیمی ما، براکتهای تست مورد استفاده در یک بررسی if/then نیست. در حقیقت، یک حلقه while به طور مجاز میتواند ساختار براکتهای دوتایی (while [[ condition ]]) کارآمدتر را استفاده کند.
مانند همان حالت حلقههای for، قرار دادن do در همان سطر بررسی شرط به یک سمیکالن نیاز دارد.
while [ condition ] ; do
توجه نمایید که براکتهای تست در یک حلقه while اجباری نیستند. برای مثال، ساختار getopts را ببینید.
#!/bin/bash var0=0 LIMIT=10 while [ "$var0" -lt "$LIMIT" ] # # do echo -n "$var0 " #-n سطر جدید را موقوف میکند. #فاصله، برای جدا کردن اعداد چاپ شده. var0=`expr $var0 + 1` #var0=$(($var0+1)) نیز کار میکند. #var0=$((var0 + 1)) هم کار میکند. #let "var0 += 1" نیز کار میکند. done # echo exit 0
مثال 11-16. یک حلقه while دیگر
#!/bin/bash echo while [ "$var1" != "end" ] #معادل است با while test "$var1" != "end" do echo "Input variable #1 (end to exit) " read var1 #از read $var1 استفاده نشده (چرا؟). echo "variable #1 = $var1" #به علت وجود # نقلقولها لازم هستند. #اگر ورودی end باشد، اینجا آن را نشان میدهد. # echo done exit 0
یک حلقه while ممکن است دارای چند شرط باشد. فقط شرط انتهایی تعیین میکند که حلقه چه وقت خاتمه یابد. اگر چه، این یک تفاوت ناچیز را ایجاب مینماید.
مثال 11-17. حلقه while با چند شرط
#!/bin/bash var1=unset previous=$var1 while echo "previous-variable = $previous" echo previous=$var1 [ "$var1" != end ] #کسب اطلاع از آنچه $var1 قبلاً بوده. #چهار وضعیت در while، اما فقط مورد انتهایی، حلقه را کنترل میکند. # do echo "Input variable #1 (end to exit) " read var1 echo "variable #1 = $var1" done # # exit 0
همچون حلقه for، یک حلقه while نیز میتواند با استفاده از ساختار پرانتزهای دوتایی، ترکیب C-style را به کار ببرد ( مثال 8-5 را هم ببینید).
مثال 11-18. ترکیب C-style در یک حلقه while
#!/bin/bash #شمارش تا 10 در یک حلقه while LIMIT=10 # a=1 while [ "$a" -le $LIMIT ] do echo -n "$a " let "a+=1" done # echo; echo # +=================================================================+ #اکنون، با ترکیب C-مانند تکرار خواهیم نمود. ((a = 1)) # #پرانتزهای دوتایی، همچون در C درج فاصله را هنگام تنظیم متغیر اجازه میدهند. while (( a <= LIMIT )) #پرانتزهای دوتایی و عدم استفاده از $ مقدم متغیرها do echo -n "$a " ((a += 1)) #معادل با let "a+=1" # #پرانتزهای دوتایی افزایش یک متغیر با ترکیب دستوری مانند C را مجاز میسازند. done echo #برنامهنویسان C و Java میتوانند در Bash احساس راحتی نمایند. exit 0
یک حلقه while میتواند داخل براکتهای تست خود، یک تابع را فراخوانی کند.
t=0 condition () { ((t++)) if [ $t -lt 5 ] then return 0 # else return 1 # fi } while condition # #احضار تابع -- چهار بار تکرار حلقه. do echo "Still going: t = $t" done # # # #
مشابه ساختار if-test، در یک حلقه while براکتهای تست میتوانند حذف بشوند. while condition do command(s) ... done |
توسط بهم وصل کردن قدرت فرمان read با یک حلقه while، ساختار سودمند while read مفید برای خواندن و تجزیه فایلها را به دست میآوریم.
cat $filename | # while read line # do ... done # while read value # do rt=$(echo "scale=$SC; $rt + $value" | bc) (( ct++ )) done am=$(echo "scale=$SC; $rt / $ct" | bc) echo $am; return $ct # #توجه: این ترفند کوچک در صورتیکه $ct بزرگتر از 255 باشد کار #نمیکند! برای کار کردن با یک تعداد بزرگتری از واحدهای داده، #+به سادگی "return $ct" بالا را به حالت توضیح درآورید. } <"$datafile" #
یک حلقه while ممکن است ورودی استانداردش را توسط یک < در انتهایش به یک فایل تغییر مسیر داده باشد.
یک حلقه while ممکن است ورودی استانداردش را توسط یک لوله فراهم کرده باشد. |
این ساختار، شرطی را در ابتدای حلقه بررسی میکند، و تا زمانیکه آن شرط غلط باشد (بر عکس حلقه while) حلقه را حفظ میکند.
until [ condition-is-true ]
do
command(s)...
done
توجه نمایید که حلقه until شرط خاتمه دادن را در بالای حلقه بررسی میکند، متفاوت با ساختار مشابه آن در برخی زبانهای برنامهنویسی است.
همچون در مورد حلقههای for، قرار دادن do در همان سطرِ شرط نیازمند یک سمیکالن است.
until [ condition-is-true ] ; do
#!/bin/bash END_CONDITION=end until [ "$var1" = "$END_CONDITION" ] # do echo "Input variable #1 " echo "($END_CONDITION to exit)" read var1 echo "variable #1 = $var1" echo done #همچون همراه با حلقههای "for" و "while" یک حلقه #+"until" نیز ساختارهای تست C-مانند را اجازه میدهد. LIMIT=10 var=0 until (( var > LIMIT )) do #نه براکتها، نه $ پیشوندی متغیرها. echo -n "$var " (( var++ )) done # exit 0
چطور بین یک حلقه for یا یک حلقه while یا حلقه until انتخاب کنیم؟ در C، به طور نمونه یک حلقه for را موقعی به کار میبرید که تعداد تکرار حلقه از قبل معلوم است. اما با Bash وضعیت نامعلومتر است. حلقه for در Bash به طور آزادانهتری ساخته شده و بیشتر از معادلش در سایر زبانها انعطافپذیر است. بنابراین، با خیال راحت هر نوع حلقهای را که به سادهترین روش کار را انجام میدهد استفاده کنید.
[1] |
تکرار: اجرای تکراری یک فرمان یا گروهی از فرمانها، اغلب -- اما نه همیشه، در حالیکه یک شرط مفروض برقرار است، یا تا وقتی یک شرط مفروض برقرار بشود. |