10.1. Циклы | Популярный Linux

Опубликовано Bash в Ср, 23/12/2009 — 20:59


Цикл — это блок команд, который исполняется многократно до тех пор, пока не будет выполнено условие выхода из цикла.

циклы for

for (in)

Это одна из основных разновидностей циклов. И она значительно отличается от аналога в языке C.

for arg in [list]
do
 команда(ы)
done

Note

На каждом проходе цикла, переменная-аргумент цикла arg последовательно, одно за другим, принимает значения из списка list.

  1. for arg in «$var1« «$var2« «$var3««$varN«
  2. # На первом проходе, $arg = $var1
  3. # На втором проходе, $arg = $var2
  4. # На третьем проходе, $arg = $var3
  5. # …
  6. # На N-ном проходе, $arg = $varN
  7. # Элементы списка заключены в кавычки для того, чтобы предотвратить возможное разбиение их на отдельные аргументы (слова).

Элементы списка могут включать в себя шаблонные символы.

Есл ключевое слово do находится в одной строке со словом for, то после списка аргументов (перед do) необходимо ставить точку с запятой.

for arg in [list] ; do

Пример 10-1. Простой цикл for

  1. #!/bin/bash
  2. # Список планет.
  3. for planet in Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон
  4. do
  5.   echo $planet
  6. done
  7. echo
  8. # Если ‘список аргументов’ заключить в кавычки, то он будет восприниматься как единственный аргумент .
  9. for planet in «Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон»
  10. do
  11.   echo $planet
  12. done
  13. exit 0
Note

Каждый из элементов [списка] может содержать несколько аргументов. Это бывает полезным при обработке групп параметров. В этом случае, для принудительного разбора каждого из аргументов в списке, необходимо использовать инструкцию set (см. Пример 11-14).

Пример 10-2. Цикл for с двумя параметрами в каждом из элементов списка

  1. #!/bin/bash
  2. # Список планет.
  3. # Имя кажой планеты ассоциировано с расстоянием от планеты до Солнца (млн. миль).
  4. for planet in «Меркурий 36» «Венера 67» «Земля 93»  «Марс 142» «Юпитер 483»
  5. do
  6.   set$planet  # Разбиение переменной «planet» на множество аргументов (позиционных параметров).
  7.   # Конструкция «—» предохраняет от неожиданностей, если $planet «пуста» или начинается с символа «-«.
  8.   # Если каждый из аргументов потребуется сохранить, поскольку на следующем проходе они будут «забиты» новыми значениями,
  9.   # То можно поместить их в массив,
  10.   #        original_params=(«$@»)
  11.   echo «$1      в $2,000,000 миль от Солнца»
  12.   #—-две табуляции—к параметру $2 добавлены нули
  13. done
  14. # (Спасибо S.C., за разъяснения.)
  15. exit 0

В качестве списка, в цикле for, можно использовать переменную.

Пример 10-3. Fileinfo: обработка списка файлов, находящегося в переменной

  1. #!/bin/bash
  2. # fileinfo.sh
  3. FILES=«/usr/sbin/privatepw
  4. /usr/sbin/pwck
  5. /usr/sbin/go500gw
  6. /usr/bin/fakefile
  7. /sbin/mkreiserfs
  8. /sbin/ypbind»     # Список интересующих нас файлов.
  9.                   # В список добавлен фиктивный файл /usr/bin/fakefile.
  10. echo
  11. for file in $FILES
  12. do
  13.   if [ ! -e «$file« ]       # Проверка наличия файла.
  14.   then
  15.     echo «Файл $file не найден.»; echo
  16.     continue                # Переход к следующей итерации.
  17.   fi
  18.   ls -l $file | awk ‘{ print $8 »         размер: » $5 }’  # Печать 2 полей.
  19.   whatis `basename $file`   # Информация о файле.
  20.   echo
  21. done  
  22. exit 0

В [списке] цикла for могут быть использованы имена файлов, которые в свою очередь могут содержать символы-шаблоны.

Пример 10-4. Обработка списка файлов в цикле for

  1. #!/bin/bash
  2. # list-glob.sh: Создание список файлов в цикле for с использованием
  3. # операции подстановки имен файлов («globbing»).
  4. echo
  5. for file in *
  6. do
  7.   ls -l «$file«  # Список всех файлов в $PWD (текущем каталоге).
  8.   # Напоминаю, что символу «*» соответствует любое имя файла,
  9.   # однако, в операциях подстановки имен файлов («globbing»),
  10.   # имеются исключения — имена файлов, начинающиеся с точки.
  11.   # Если в каталоге нет ни одного файла, соответствующего шаблону,
  12.   # то за имя файла принимается сам шаблон.
  13.   # Чтобы избежать этого, используйте ключ nullglob
  14.   # (shopt -s nullglob).
  15.   # Спасибо S.C.
  16. done
  17. echo; echo
  18. for file in [jx]*
  19. do
  20.   rm -f $file    # Удаление файлов, начинающихся с «j» или «x» в $PWD.
  21.   echo «Удален файл $file«.
  22. done
  23. echo
  24. exit 0

Если [список] в цикле for не задан, то в качестве оного используется переменная $@ — список аргументов командной строки. Оень остроумно эта особенность проиллюстрирована в Пример A-18.

Пример 10-5. Цикл for без списка аргументов

  1. #!/bin/bash
  2. # Попробуйте вызвать этот сценарий с аргументами и без них и посмотреть на результаты.
  3. for a
  4. do
  5.  echo -n «$a «
  6. done
  7. #  Список аргументов не задан, поэтому цикл работает с переменной ‘$@’
  8. #+ (список аргументов командной строки, включая пробельные символы).
  9. echo
  10. exit 0

При создании списка аргументов, в цикле for допускается пользоваться подстановкой команд. См. Пример 12-42, Пример 10-10 и Пример 12-36.

Пример 10-6. Создание списка аргументов в цикле for с помощью операции подстановки команд

  1. #!/bin/bash
  2. # Цикл for со [списком], созданным с помощью подстановки команд.
  3. NUMBERS=«9 7 3 8 37.53»
  4. for number in `echo $NUMBERS`  # for number in 9 7 3 8 37.53
  5. do
  6.   echo -n «$number «
  7. done
  8. echo
  9. exit 0

Более сложный пример использования подстановки команд при создании списка аргументов цикла.

Пример 10-7. grep для бинарных файлов

  1. #!/bin/bash
  2. # bin-grep.sh: Поиск строк в двоичных файлах.
  3. # замена «grep» для бинарных файлов.
  4. # Аналогично команде «grep -a»
  5. E_BADARGS=65
  6. E_NOFILE=66
  7. if [ $# -ne 2 ]
  8. then
  9.   echo «Порядок использования: `basename $0` string filename»
  10.   exit $E_BADARGS
  11. fi
  12. if [ ! -f «$2» ]
  13. then
  14.   echo «Файл $2 не найден.»
  15.   exit $E_NOFILE
  16. fi
  17. for word in $( strings «$2» | grep «$1» )
  18. # Инструкция «strings» возвращает список строк в двоичных файлах.
  19. # Который затем передается по конвейеру команде «grep», для выполнения поиска.
  20. do
  21.   echo $word
  22. done
  23. # Как указывает S.C., вышепрведенное объявление цикла for может быть упрощено
  24. #    strings «$2» | grep «$1» | tr -s «$IFS» ‘[\n*]’
  25. # Попробуйте что нибудь подобное:  «./bin-grep.sh mem /bin/ls»
  26. exit 0

Еще один пример.

Пример 10-8. Список всех пользователей системы

  1. #!/bin/bash
  2. # userlist.sh
  3. PASSWORD_FILE=/etc/passwd
  4. n=1           # Число пользователей
  5. for name in $(awk ‘BEGIN{FS=»:»}{print $1}’ < «$PASSWORD_FILE« )
  6. # Разделитель полей = :  ^^^^^^
  7. # Вывод первого поля              ^^^^^^^^
  8. # Данные берутся из файла паролей            ^^^^^^^^^^^^^^^^^
  9. do
  10.   echo «Пользователь #$n = $name«
  11.   let «n += 1»
  12. done
  13. # Пользователь #1 = root
  14. # Пользователь #2 = bin
  15. # Пользователь #3 = daemon
  16. # …
  17. # Пользователь #30 = bozo
  18. exit 0

И заключительный пример использования подстановки команд при создании [списка].

Пример 10-9. Проверка авторства всех бинарных файлов в текущем каталоге

  1. #!/bin/bash
  2. # findstring.sh:
  3. # Поиск заданной строки в двоичном файле.
  4. directory=/usr/local/bin/
  5. fstring=«Free Software Foundation»  # Поиск файлов от FSF.
  6. for file in $( find $directory -type f -name ‘*’ | sort )
  7. do
  8.   strings -f $file | grep «$fstring« | sed -e «s%$directory%%»
  9.   #  Команде «sed» передается выражение (ключ -e),
  10.   #+ для того, чтобы изменить обычный разделитель «/» строки поиска и строки замены
  11.   #+ поскольку «/» — один из отфильтровываемых символов.
  12.   #  Использование такого символа порождает сообщение об ошибке (попробуйте).
  13. done
  14. exit 0
  15. #  Упражнение:
  16. #  —————
  17. #  Измените сценарий таким образом, чтобы он брал
  18. #+ $directory и $fstring из командной строки.

Результат работы цикла for может передаваться другим командам по конвейеру.

Пример 10-10. Список символических ссылок в каталоге

  1. #!/bin/bash
  2. # symlinks.sh: Список символических ссылок в каталоге.
  3. directory=${1-`pwd`}
  4. #  По-умолчанию в текущем каталоге,
  5. #  Блок кода, который выполняет аналогичные действия.
  6. # ———————————————————-
  7. # ARGS=1                 # Ожидается один аргумент командной строки.
  8. #
  9. # if [ $# -ne «$ARGS» ]  # Если каталог поиска не задан…
  10. # then
  11. #   directory=`pwd`      # текущий каталог
  12. # else
  13. #   directory=$1
  14. # fi
  15. # ———————————————————-
  16. echo «символические ссылки в каталоге $directory«
  17. for file in «$( find $directory -type l )«   # -type l = символические ссылки
  18. do
  19.   echo «$file«
  20. done | sort             # В противном случае получится неотсортированный список.
  21. #  Как отмечает Dominik ‘Aeneas’ Schnitzer,
  22. #+ в случае отсутствия кавычек для $( find $directory -type l )
  23. #+ сценарий «подавится» именами файлов, содержащими пробелы.
  24. exit 0

Вывод цикла может быть перенаправлен со stdout в файл, ниже приводится немного модифицированный вариант предыдущего примера, демонстрирующий эту возможность.

Пример 10-11. Список символических ссылок в каталоге, сохраняемый в файле

  1. #!/bin/bash
  2. # symlinks.sh: Список символических ссылок в каталоге.
  3. OUTFILE=symlinks.list                         # файл со списком
  4. directory=${1-`pwd`}
  5. #  По-умолчанию — текущий каталог,
  6. echo «символические ссылки в каталоге $directory« > «$OUTFILE«
  7. echo «—————————« >> «$OUTFILE«
  8. for file in «$( find $directory -type l )«    # -type l = символические ссылки
  9. do
  10.   echo «$file«
  11. done | sort >> «$OUTFILE«                     # перенаправление вывода
  12. #           ^^^^^^^^^^^^^                       в файл.
  13. exit 0

Оператор цикла for имеет и альтернативный синтаксис записи — очень похожий на синтаксис оператора for в языке C. Для этого используются двойные круглые скобки.

Пример 10-12. C-подобный синтаксис оператора цикла for

  1. #!/bin/bash
  2. # Два вапианта оформления цикла.
  3. echo
  4. # Стандартный синтаксис.
  5. for a in 1 2 3 4 5 6 7 8 9 10
  6. do
  7.   echo -n «$a «
  8. done
  9. echo; echo
  10. # +==========================================+
  11. # А теперь C-подобный синтаксис.
  12. LIMIT=10
  13. for ((a=1; a <= LIMIT ; a++))  # Двойные круглые скобки и «LIMIT» без «$».
  14. do
  15.   echo -n «$a «
  16. done                           # Конструкция заимствована из ‘ksh93’.
  17. echo; echo
  18. # +=========================================================================+
  19. # Попробуем и C-шный оператор «запятая».
  20. for ((a=1, b=1; a <= LIMIT ; a++, b++))  # Запятая разделяет две операции, которые выполняются совместно.
  21. do
  22.   echo -n «$a$b «
  23. done
  24. echo; echo
  25. exit 0

См. так же Пример 25-15, Пример 25-16 и Пример A-7.

А сейчас пример сценария, который может найти «реальное» применение.

Пример 10-13. Работа с командой efax в пакетном режиме

  1. #!/bin/bash
  2. EXPECTED_ARGS=2
  3. E_BADARGS=65
  4. if [ $# -ne $EXPECTED_ARGS ]
  5. # Проверка наличия аргументов командной строки.
  6. then
  7.    echo «Порядок использования: `basename $0` phone# text-file»
  8.    exit $E_BADARGS
  9. fi
  10. if [ ! -f «$2» ]
  11. then
  12.   echo «Файл $2 не является текстовым файлом»
  13.   exit $E_BADARGS
  14. fi
  15. fax make $2              # Создать fax-файлы из текстовых файлов.
  16. for file in $(ls $2.0*)  # Все файлы, получившиеся в результате преобразования.
  17.                          # Используется шаблонный символ в списке.
  18. do
  19.   fil=«$fil $file«
  20. done
  21. efax -d /dev/ttyS3 -o1 -t «T$1» $fil   # отправить.
  22. # Как указывает S.C., в цикл for может быть вставлена сама команда отправки в виде:
  23. #    efax -d /dev/ttyS3 -o1 -t «T$1» $2.0*
  24. # но это не так поучительно [;-)].
  25. exit 0
while

Оператор while проверяет условие перед началом каждой итерации и если условие истинно (если код возврата равен 0), то управление передается в тело цикла. В отличие от циклов for, циклы while используются в тех случаях, когда количество итераций заранее не известно.

while [condition]
do
 command
done

Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ «;» перед do.

while [condition] ; do

Обратите внимание: в отдельных случаях, таких как использование конструкции getopts совместно с оператором while, синтаксис несколько отличается от приводимого здесь.

Пример 10-14. Простой цикл while

  1. #!/bin/bash
  2. var0=0
  3. LIMIT=10
  4. while [ «$var0« -lt «$LIMIT« ]
  5. do
  6.   echo -n «$var0 «        # -n подавляет перевод строки.
  7.   var0=`expr $var0 + 1`   # допускается var0=$(($var0+1)).
  8. done
  9. echo
  10. exit 0

Пример 10-15. Другой пример цикла while

  1. #!/bin/bash
  2. echo
  3. while [ «$var1« != «end» ]     # возможна замена на while test «$var1» != «end»
  4. do
  5.   echo «Введите значение переменной #1 (end — выход) «
  6.   read var1                    # Конструкция ‘read $var1’ недопустима (почему?).
  7.   echo «переменная #1 = $var1« # кавычки обязательны, потому что имеется символ «#».
  8.   # Если введено слово ‘end’, то оно тоже выводится на экран.
  9.   # потому, что проверка переменной выполняется в начале итерации (перед вводом).
  10.   echo
  11. done  
  12. exit 0

Оператор while может иметь несколько условий. Но только последнее из них определяет возможность продолжения цикла. В этом случае синтаксис оператора цикла должен быть несколько иным.

Пример 10-16. Цикл while с несколькими условиями

  1. #!/bin/bash
  2. var1=unset
  3. previous=$var1
  4. while echo «предыдущее значение = $previous«
  5.       echo
  6.       previous=$var1     # запомнить предыдущее значение
  7.       [ «$var1« != end ]
  8.       # В операторе «while» присутствуют 4 условия, но только последнее управляет циклом.
  9.       # *последнее* условие — единственное, которое вычисляется.
  10. do
  11. echo «Введите значение переменной #1 (end — выход) «
  12.   read var1
  13.   echo «текущее значение = $var1«
  14. done
  15. # попробуйте самостоятельно разобраться в сценарии works.
  16. exit 0

Как и в случае с for, цикл while может быть записан в C-подобной нотации, с использованием двойных круглых скобок (см. так же Пример 9-29).

Пример 10-17. C-подобный синтаксис оформления цикла while

  1. #!/bin/bash
  2. # wh-loopc.sh: Цикл перебора от 1 до 10.
  3. LIMIT=10
  4. a=1
  5. while [ «$a« -le $LIMIT ]
  6. do
  7.   echo -n «$a «
  8.   let «a+=1»
  9. done           # Пока ничего особенного.
  10. echo; echo
  11. # +=================================================================+
  12. # А теперь оформим в стиле языка C.
  13. ((a = 1))      # a=1
  14. # Двойные скобки допускают наличие лишних пробелов в выражениях.
  15. while (( a <= LIMIT ))   # В двойных скобках символ «$» перед переменными опускается.
  16. do
  17.   echo -n «$a «
  18.   ((a += 1))   # let «a+=1»
  19.   # Двойные скобки позволяют наращивание переменной в стиле языка C.
  20. done
  21. echo
  22. # Теперь, программисты, пишущие на C, могут чувствовать себя в Bash как дома.
  23. exit 0
Note

Стандартное устройство ввода stdin, для цикла while, можно перенаправить на файл с помощью команды перенаправления < в конце цикла.

until

Оператор цикла until проверяет условие в начале каждой итерации, но в отличие от while итерация возможна только в том случае, если условие ложно.

until [condition-is-true]
do
 command
done

Обратите внимание: оператор until проверяет условие завершения цикла ПЕРЕД очередной итерацией, а не после, как это принято в некоторых языках программирования.

Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ «;» перед do.

until [condition-is-true] ; do

Пример 10-18. Цикл until

  1. #!/bin/bash
  2. until [ «$var1« = end ] # Проверка условия производится в начале итерации.
  3. do
  4.   echo «Введите значение переменной #1 «
  5.   echo «(end — выход)»
  6.   read var1
  7.   echo «значение переменной #1 = $var1«
  8. done  
  9. exit 0

Запись опубликована в рубрике Без рубрики. Добавьте в закладки постоянную ссылку.