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

‎11.3‎- کنترل حلقه

 

Tournez cent tours, tournez mille tours,

Tournez souvent et tournez toujours . . .

--Verlaine(م: شاعر فرانسوی), "Chevaux de bois"

فرمانهای اثر گذار بر رفتار حلقه

break, continue

فرمان‌های کنترل حلقه break و continue‎[1]‎ به طور دقیق با همتاهایشان در سایر زبانهای برنامه‌نویسی مطابقت دارند. فرمان break حلقه را خاتمه می‌دهد (از آن بیرون می‌رود)، در حالیکه continue با پرش از روی تمام فرمانهای باقیمانده در آن دور بخصوص حلقه، باعث جهیدن به تکرار بعدی حلقه می‌گردد.

مثال ‎11-21‎. اثرات break و continue در یک حلقه

#!/bin/bash

LIMIT=19  # حد بالایی

echo
echo "Printing Numbers 1 through 20 (but not 3 and 11)."

a=0

while [ $a -le "$LIMIT" ]
do
 a=$(($a+1))

 if [ "$a" -eq 3 ] || [ "$a" -eq 11 ]  #  به غیر از ‎3‎ و ‎11‎
 then
   continue      # .پرش از باقیمانده این تکرار بخصوص حلقه
 fi

 echo -n "$a "   #        این سطر برای ‎3‎ و ‎11‎ اجرا نمی‌شود.
done 

#                    :تمرین
#  چرا حلقه تا ‎20‎ چاپ می‌کند؟

echo; echo

echo Printing Numbers 1 through 20, but something happens after 2.

##################################################################

# همان حلقه، اما با جایگزینی ‎break‎ به جای ‎continue‎

a=0

while [ "$a" -le "$LIMIT" ]
do
 a=$(($a+1))

 if [ "$a" -gt 2 ]
 then
   break  # .پرش از تمام باقیمانده حلقه
 fi

 echo -n "$a "
done

echo; echo; echo

exit 0

فرمان break ممکن است به طور اختیاری یک پارامتر بگیرد. یک break ساده فقط درونی‌ترین حلقه‌ای را که در آن جاسازی گردیده است، خاتمه می‌دهد، اما یک ‎break N‎ از N رده حلقه بیرون می‌رود.

مثال ‎11-22‎. بیرون رفتن از چند رده حلقه

#!/bin/bash
# ‎break-levels.sh:‎ بیرون رفتن از حلقه‌ها

# ‎"break N"‎ از ‎N‎ سطح حلقه‌ها بیرون می‌رود.

for outerloop in 1 2 3 4 5
do
  echo -n "Group $outerloop:   "

  # --------------------------------------------------------
  for innerloop in 1 2 3 4 5
  do
    echo -n "$innerloop "

    if [ "$innerloop" -eq 3 ]
    then
      break  # ‎break 2‎ را برای دیدن اینکه چه می‌شود، امتحان کنید.
             #      ( از هر دو حلقه داخلی و خارجی بیرون می‌رود. )
    fi
  done
  # --------------------------------------------------------

  echo
done  

echo

exit 0

فرمان continue نیز، همانند break، به طور اختیاری یک پارامتر قبول می‌کند. یک continue ساده، تکرار فعلی داخل حلقه‌اش را پیش از موقع قطع کرده و دور بعدی را شروع می‌کند. یک ‎continue N‎ تمام تکرارهای باقیمانده سطح حلقه‌اش را فسخ می‌کند و تکرار بعدی حلقه را در N سطح بالاتر ادامه می‌دهد.

مثال ‎11-23‎. ادامه در سطح بالاتر حلقه

#!/bin/bash
# فرمان ‎"continue N"‎ در Nمین سطح حلقه ادامه می‌دهد.

for outer in I II III IV V           # حلقه خارجی
do
  echo; echo -n "Group $outer: "

  # --------------------------------------------------------------------
  for inner in 1 2 3 4 5 6 7 8 9 10  # حلقه داخلی
  do

    if [[ "$inner" -eq 7 && "$outer" = "III" ]]
    then
      continue 2  # .ادامه حلقه در دومین سطح، یعنی در «حلقه خارجی»‏
                  #   برای دیدن رفتار معمول حلقه، سطر بالا را با یک
                  #                     ‎"continue"‎ ساده تعویض کنید.
    fi  

    echo -n "$inner "   #  ‎7 8 9 10‎ در «گروه ‎III‎» منعکس نخواهند شد.
  done  
  # --------------------------------------------------------------------

done

echo; echo

#                            :تمرین
# یک کاربرد با معنی برای ‎"continue N"‎ در یک اسکریپت طرح کنید.

exit 0

مثال ‎11-24‎. کاربرد ‎continue N‎ در یک وظیفه واقعی

# ‎Albert Reiner‎ یک نمونه از چگونگی استفاده از ‎"continue N"‎ را ارایه می‌کند:
# ---------------------------------------------------------

#  فرض کنید من وظایف بسیاری دارم که لازم است با هر داده‌ای اجرا بشود
#+ یعنی روی فایلهایی با یک الگوی نام تعیین شده در یک دایرکتوری عمل
#+ بشود. چندین ماشین وجود دارد که به این دایرکتوری دسترسی دارند، و
#+             .من می‌خواهم کار را روی این ماشین‌های مختلف توزیع کنم
#      آنوقت معمولاً چیزی مشابه مورد زیر را با هر دستگاه ‎nohup‎ می‌کنم:

while true
do
  for n in .iso.*
  do
    [ "$n" = ".iso.opts" ] && continue
    beta=${n#.iso.}
    [ -r .Iso.$beta ] && continue
    [ -r .lock.$beta ] && sleep 10 && continue
    lockfile -r0 .lock.$beta || continue
    echo -n "$beta: " `date`
    run-isotherm $beta
    date
    ls -alF .Iso.$beta
    [ -r .Iso.$beta ] && rm -f .lock.$beta
    continue 2
  done
  break
done

exit 0

#   جزییات، محصوصاً ‎sleep N‎ ویژه برنامه کاربردی
#+     :من هستند، اما الگوی کلی عبارت است از

while true
do
  for job in {pattern}
  do
    {job already done or running} && continue
    {mark job as running, do job, mark job as done}
    continue 2
  done
  break        # یا چیزی مانند ‎'sleep 600'‎ برای اجتناب از خاتمه یافتن.
done

#  بدین طریق اسکریپت فقط موقعی متوقف خواهد شد که کار دیگری برای اجرا وجود
#+  نداشته باشد (از جمله کارهایی که در حین اجرا اضافه گردیده‌اند). از طریق
#+کاربرد فایل‌های قفل مناسب می‌تواند بدون دونسخه‌ای کردن محاسبات (که در مورد
#+ من بیش از ساعت‌ها کار می‌برد، از اینرو می‌خواهم از آن پرهیز نمایم) به طور
#+همزمان در چندین ماشین اجرا بشود. همچنین چون جستجو همیشه دوباره از ابتدا
#+    شروع می‌شود، شخص می‌تواند اولویت در نام فایلها را نیز کد گذاری نماید.
#+البته شخص می‌تواند این کار را بدون «‎continue 2‎» نیز انجام دهد، اما آنوقت
#+می‌باید به طور واقعی کنترل می‌کرد که به هر حال کار معینی انجام شده یا خیر‏
#+                     (برای اینکه ما باید بلافاصله سراغ کار بعدی می‌رفتیم)
#+(در حالتی که قبل از کنترل برای یک کار جدید، خاتمه بدهیم یا به مدت طولانی
#+                                                       غیر فعال بشویم.)

Caution

درک ساختار ‎continue N‎ دشوار و استفاده از آن در یک مضمون معنادار، مهارت‌آمیز است. شاید پرهیز از آن مناسب‌تر باشد.

یادداشت‌ها

[1]

این فرمانها builtin پوسته هستند، در حالیکه سایر فرمانهای حلقه، از قبیل while و case، کلمه کلیدی هستند.