فصل ‎11‎- حلقه‌ها و انشعاب‌ها

‎11.1‎- حلقه‌ها

یک حلقه، قطعه کدی است که مشروط به صحیح بودن شرط کنترل حلقه، لیستی از فرمانها را تکرار می‌کند. ‎[1]‎

حلقه‌های for

‎for arg in [list]

این ساختار اصلی ایجاد حلقه است. به طور قابل توجهی با شکل همتایش در 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
# fileinfo.sh

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‎ ...
# globbing =‎ بسط نام فایل

echo

for file in *
#           ‎^‎  تشخیص ‎globbing‎  بر عبارتهایی که ‎Bash‎ 
#+                  .بدهد، بسط نام فایل انجام می‌دهد
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
# userlist.sh

PASSWORD_FILE=/etc/passwd
n=1           # شماره کاربر

for name in $(awk 'BEGIN{FS=":"}{print $1}' < "$PASSWORD_FILE" )
#                         ‎:‎ جداکننده فیلد است       ^^^^^^
#                                 چاپ فیلد اول    ^^^^^^^^
#می‌گیرد  ‎/etc/passwd‎  ورودی را از فایل پسورد  ‎^^^^^^^^^^^^^^^^‎
do
  echo "USER #$n = $name"
  let "n += 1"
done  


# USER #1 = root
# USER #2 = bin
# USER #3 = daemon
# ...
# USER #33 = bozo

exit $?

#                             :مباحثه
#  ----------
#    چطور است که یک کاربر معمولی، یا اسکریپت اجرا شده توسط او می‌تواند فایل
#+ ‎/etc/passwd‎ را بخواند؟ (اشاره: مجوزهای فایل ‎/etc/passwd‎ را بررسی کنید.)
#                          آیا این یک حفره امنیتی است؟ چرا هست یا چرا نیست؟

بازهم یک مثال دیگر از ‎[list]‎ حاصل از جایگزینی فرمان.

مثال ‎11-10‎. کنترل تمام فایلهای باینری در یک دایرکتوری برای تعیین هویت نویسنده

#!/bin/bash
# findstring.sh:
#.یافتن یک رشته بخصوص در فایلهای باینری داخل دایرکتوری تعیین شده

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

# one
# two
# three

خروجی یک حلقه 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"                    #         خروجی استاندارد حلقه
#           ^^^^^^^^^^^^^                    به فایل ذخیره تغییر مسیر یافته

# echo "Output file = $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++)) 
# ‎do‎ نیست!
{
  echo -n "* $n *"
}
# ‎done‎ نیست!


#                    :خروجی‌ها
# * 1 ** 2 ** 3 ** 4 ** 5 ** 6 ** 7 ** 8 ** 9 ** 10 *
# و ‎echo $?‎ مقدار ‎0‎ را برگشت می‌دهد، بنابراین ‎Bash‎ خطا گزارش نمی‌کند.


echo


# اما، توجه کنید که در یک حلقه کلاسیک ‎for‎ به شکل ‎for n in [list] ...‎
#+                          .یک کاراکتر سمی‌کالن انتهایی لازم می‌گردد

for n in 1 2 3
{  echo -n "$n "; }
#               ^


# با تشکر از ‎YongYe‎ برای توضیح این مورد.


while

این ساختار، یک شرط را در ابتدای یک حلقه بررسی می‌کند، و تا وقتی که آن شرط صحیح باشد (یک وضعیت خروج 0 برگشت بدهد) حلقه را حفظ می‌کند. در مقایسه با حلقه for، یک حلقه while در وضعیت‌هایی که تعداد تکرار از قبل شناخته شده نیست مورد استفاده می‌یابد.

while [ condition ]
do
 command(s)...
done

ساختار براکت در یک حلقه while چیزی غیراز دوست قدیمی ما، براکت‌های تست مورد استفاده در یک بررسی ‎if/then‎ نیست. در حقیقت، یک حلقه while به طور مجاز می‌تواند ساختار براکت‌های دوتایی ‎(while [[ condition ]])‎ کارآمدتر را استفاده کند.

مانند همان حالت حلقه‌های for، قرار دادن do در همان سطر بررسی شرط به یک سمی‌کالن نیاز دارد.

while [ condition ] ; do

توجه نمایید که براکت‌های تست در یک حلقه while اجباری نیستند. برای مثال، ساختار getopts را ببینید.

مثال ‎11-15‎. حلقه ساده while

#!/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
# wh-loopc.sh:  شمارش تا ‎10‎ در یک حلقه while

LIMIT=10                 #    .بار تکرار‎10‎
a=1

while [ "$a" -le $LIMIT ]
do
  echo -n "$a "
  let "a+=1"
done                     # .تا اینجا، شگفت‌آور نیست

echo; echo

# +=================================================================+

#  اکنون، با ترکیب C-مانند تکرار خواهیم نمود.

((a = 1))                 #  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  # true
  else
    return 1  # false
  fi
}

while condition
#     ^^^^^^^^^
#     احضار تابع -- چهار بار تکرار حلقه.
do
  echo "Still going: t = $t"
done

# Still going: t = 1
# Still going: t = 2
# Still going: t = 3
# Still going: t = 4

توسط بهم وصل کردن قدرت فرمان read با یک حلقه while، ساختار سودمند while read مفید برای خواندن و تجزیه فایلها را به دست می‌آوریم.

cat $filename |   #                         .تهیه ورودی از یک فایل
while read line   # .تا موقعی که یک سطر دیگر برای خواندن وجود دارد
do
  ...
done

# ============ ‎"sd.sh"‎ قطعه‌ای از اسکریپت نمونه ============ #

  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 ممکن است ورودی استانداردش را توسط یک لوله فراهم کرده باشد.


until

این ساختار، شرطی را در ابتدای حلقه بررسی می‌کند، و تا زمانیکه آن شرط غلط باشد (بر عکس حلقه while) حلقه را حفظ می‌کند.

until [ condition-is-true ]
do
 command(s)...
done

توجه نمایید که حلقه until شرط خاتمه دادن را در بالای حلقه بررسی می‌کند، متفاوت با ساختار مشابه آن در برخی زبانهای برنامه‌نویسی است.

همچون در مورد حلقه‌های for، قرار دادن do در همان سطرِ شرط نیازمند یک سمی‌کالن است.

until [ condition-is-true ] ; do

مثال ‎11-19‎. حلقه until

#!/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    # 0 1 2 3 4 5 6 7 8 9 10 


exit 0

چطور بین یک حلقه for یا یک حلقه while یا حلقه until انتخاب کنیم؟ در C، به طور نمونه یک حلقه for را موقعی به کار می‌برید که تعداد تکرار حلقه از قبل معلوم است. اما با Bash وضعیت نامعلوم‌تر است. حلقه for در Bash به طور آزادانه‌تری ساخته شده و بیشتر از معادلش در سایر زبانها انعطاف‌پذیر است. بنابراین، با خیال راحت هر نوع حلقه‌ای را که به ساده‌ترین روش کار را انجام می‌دهد استفاده کنید.

یادداشت‌ها

[1]

تکرار: اجرای تکراری یک فرمان یا گروهی از فرمانها، اغلب -- اما نه همیشه، در حالیکه یک شرط مفروض برقرار است، یا تا وقتی یک شرط مفروض برقرار بشود.