10.3. Управление ходом выполнения цикла


break, continue

Для управления ходом выполнения цикла служат команды break и continue [23] и точно соответствуют своим аналогам в других языках программирования. Команда break прерывает исполнение цикла, в то время как continue передает управление в начало цикло, минуя все последующие команды в теле цикла.

Пример 10-20. Команды break и continue в цикле

  1. #!/bin/bash
  2. LIMIT=19  # Верхний предел
  3. echo
  4. echo "Печать чисел от 1 до 20 (исключая 3 и 11)."
  5. a=0
  6. while [ $a -le "$LIMIT" ]
  7. do
  8.  a=$(($a+1))
  9.  if [ "$a" -eq 3 ] || [ "$a" -eq 11 ]  # Исключить 3 и 11
  10.  then
  11.    continue  # Переход в начало цикла.
  12.  fi
  13.  echo -n "$a "
  14. done
  15. # Упражнение:
  16. # Почему число 20 тоже выводится?
  17. echo; echo
  18. echo Печать чисел от 1 до 20, но взгляните, что происходит после вывода числа 2
  19. ##################################################################
  20. # Тот же цикл, только 'continue' заменено на 'break'.
  21. a=0
  22. while [ "$a" -le "$LIMIT" ]
  23. do
  24.  a=$(($a+1))
  25.  if [ "$a" -gt 2 ]
  26.  then
  27.    break  # Завершение работы цикла.
  28.  fi
  29.  echo -n "$a "
  30. done
  31. echo; echo; echo
  32. exit 0

Команде break может быть передан необязательный параметр. Команда break без параметра прерывает тот цикл, в который она вставлена, а break N прерывает цикл, стоящий на N уровней выше (причем 1-й уровень — это уровень текущего цикла, прим. перев.).

Пример 10-21. Прерывание многоуровневых циклов

  1. #!/bin/bash
  2. # break-levels.sh: Прерывание циклов.
  3. # "break N" прерывает исполнение цикла, стоящего на N уровней выше текущего.
  4. for outerloop in 1 2 3 4 5
  5. do
  6.   echo -n "Группа $outerloop:   "
  7.   for innerloop in 1 2 3 4 5
  8.   do
  9.     echo -n "$innerloop "
  10.     if [ "$innerloop" -eq 3 ]
  11.     then
  12.       break  # Попробуйте "break 2",
  13.              # тогда будут прерываться как вложенный, так и внешний циклы
  14.     fi
  15.   done
  16.   echo
  17. done  
  18. echo
  19. exit 0

Команда continue, как и команда break, может иметь необязательный параметр. В простейшем случае, команда continue передает управление в начало текущего цикла, а команда continue N прерывает исполнение текущего цикла и передает управление в начало внешнего цикла, отстоящего от текущего на N уровней (причем 1-й уровень — это уровень текущего цикла, прим. перев.).

Пример 10-22. Передача управление в начало внешнего цикла

  1. #!/bin/bash
  2. # Команда "continue N" передает управление в начало внешнего цикла, отстоящего от текущего на N уровней.
  3. for outer in I II III IV V           # внешний цикл
  4. do
  5.   echo; echo -n "Группа $outer: "
  6.   for inner in 1 2 3 4 5 6 7 8 9 10  # вложенный цикл
  7.   do
  8.     if [ "$inner" -eq 7 ]
  9.     then
  10.       continue 2  # Передача управления в начало цикла 2-го уровня.
  11.                   # попробуйте убрать параметр 2 команды "continue"
  12.     fi
  13.     echo -n "$inner "  # 8 9 10 никогда не будут напечатаны.
  14.   done
  15. done
  16. echo; echo
  17. # Упражнение:
  18. # Подумайте, где реально можно использовать "continue N" в сценариях.
  19. exit 0

Пример 10-23. Живой пример использования "continue N"

  1. # Albert Reiner привел пример использования "continue N":
  2. # ---------------------------------------------------------
  3. #  Допустим, у меня есть большое количество задач, обрабатывающие некоторые данные,
  4. #+ которые хранятся в некоторых файлах, с именами, задаваемыми по шаблону,
  5. #+ в заданном каталоге.
  6. #+ Есть несколько машин, которым открыт доступ к этому каталогу
  7. #+ и я хочу распределить обработку информации между машинами.
  8. #+ тогда я обычно для каждой машины пишу нечто подобное:
  9. while true
  10. do
  11.   for n in .iso.*
  12.   do
  13.     [ "$n" = ".iso.opts" ] && continue
  14.     beta=${n#.iso.}
  15.     [ -r .Iso.$beta ] && continue
  16.     [ -r .lock.$beta ] && sleep 10 && continue
  17.     lockfile -r0 .lock.$beta || continue
  18.     echo -n "$beta: " `date`
  19.     run-isotherm $beta
  20.     date
  21.     ls -alF .Iso.$beta
  22.     [ -r .Iso.$beta ] && rm -f .lock.$beta
  23.     continue 2
  24.   done
  25.   break
  26. done
  27. #  Конкретная реализация цикла, особенно sleep N, зависит от конкретных применений,
  28. #+ но в общем случае он строится по такой схеме:
  29. while true
  30. do
  31.   for job in {шаблон}
  32.   do
  33.     {файл уже обработан или обрабатывается} && continue
  34.     {пометить файл как обрабатываемый, обработать, пометить как обработанный}
  35.     continue 2
  36.   done
  37.   break        # Или что нибудь подобное `sleep 600', чтобы избежать завершения.
  38. done
  39. #  Этот сценарий завершит работу после того как все данные будут обработаны
  40. #+ (включая данные, которые поступили во время обработки). Использование
  41. #+ соответствующих lock-файлоа позволяет вести обработку на нескольких машинах
  42. #+ одновременно, не производя дублирующих вычислений [которые, в моем случае,
  43. #+ выполняются в течении нескольких часов, так что для меня это очень важно].
  44. #+ Кроме того, поскольку поиск необработанных файлов всегда начинается с
  45. #+ самого начала, можно задавать приоритеты в именах файлов. Конечно, можно
  46. #+ обойтись и без `continue 2', но тогда придется ввести дополнительную
  47. #+ проверку — действительно ли был обработан тот или иной файл
  48. #+ (чтобы перейти к поиску следующего необработанного файла).
Caution

Конструкция continue N довольно сложна в понимании и применении, поэтому, вероятно лучше будет постараться избегать ее использования.


[23]    Эти команды являются встроенными командами языка сценариев командной оболочки (shell), в то время как while, case и т.п. — являются зарезервированными словами.