Advanced Bash-Scripting Guide: Искусство программирования на языке сценариев командной оболочки | ||
---|---|---|
Назад | Вперед |
Управление ходом исполнения -- один из ключевых моментов структурной организации сценариев на языке командной оболочки. Циклы и преходы являются теми инструментальными средствами, которые обеспечивают управление порядком исполнения команд.
Цикл -- это блок команд, который исполняется многократно до тех пор, пока не будет выполнено условие выхода из цикла.
Это одна из основных разновидностей циклов. И она значительно отличается от аналога в языке C.
for arg in [list]
do
команда(ы)...
done
На каждом проходе цикла, переменная-аргумент цикла arg последовательно, одно за другим, принимает значения из списка list. |
for arg in "$var1" "$var2" "$var3" ... "$varN" # На первом проходе, $arg = $var1 # На втором проходе, $arg = $var2 # На третьем проходе, $arg = $var3 # ... # На N-ном проходе, $arg = $varN # Элементы списка заключены в кавычки для того, чтобы предотвратить возможное разбиение их на отдельные аргументы (слова).
Элементы списка могут включать в себя шаблонные символы.
Есл ключевое слово do находится в одной строке со словом for, то после списка аргументов (перед do) необходимо ставить точку с запятой.
for arg in [list] ; do
Пример 10-1. Простой цикл for
#!/bin/bash # Список планет. for planet in Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон do echo $planet done echo # Если 'список аргументов' заключить в кавычки, то он будет восприниматься как единственный аргумент . for planet in "Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон" do echo $planet done exit 0
Каждый из элементов [списка] может содержать несколько аргументов. Это бывает полезным при обработке групп параметров. В этом случае, для принудительного разбора каждого из аргументов в списке, необходимо использовать инструкцию set (см. Пример 11-13).
Пример 10-2. Цикл for с двумя параметрами в каждом из элементов списка
#!/bin/bash # Список планет. # Имя кажой планеты ассоциировано с расстоянием от планеты до Солнца (млн. миль). for planet in "Меркурий 36" "Венера 67" "Земля 93" "Марс 142" "Юпитер 483" do set -- $planet # Разбиение переменной "planet" на множество аргументов (позиционных параметров). # Конструкция "--" предохраняет от неожиданностей, если $planet "пуста" или начинается с символа "-". # Если каждый из аргументов потребуется сохранить, поскольку на следующем проходе они будут "забиты" новыми значениями, # То можно поместить их в массив, # original_params=("$@") echo "$1 в $2,000,000 миль от Солнца" #----две табуляции---к параметру $2 добавлены нули done # (Спасибо S.C., за разъяснения.) exit 0В качестве списка, в цикле for, можно использовать переменную.
Пример 10-3. Fileinfo: обработка списка файлов, находящегося в переменной
#!/bin/bash # fileinfo.sh FILES="/usr/sbin/privatepw /usr/sbin/pwck /usr/sbin/go500gw /usr/bin/fakefile /sbin/mkreiserfs /sbin/ypbind" # Список интересующих нас файлов. # В список добавлен фиктивный файл /usr/bin/fakefile. echo for file in $FILES do if [ ! -e "$file" ] # Проверка наличия файла. then echo "Файл $file не найден."; echo continue # Переход к следующей итерации. fi ls -l $file | awk '{ print $8 " размер: " $5 }' # Печать 2 полей. whatis `basename $file` # Информация о файле. echo done exit 0В [списке] цикла for могут быть использованы имена файлов, которые в свою очередь могут содержать символы-шаблоны.
Пример 10-4. Обработка списка файлов в цикле for
#!/bin/bash # list-glob.sh: Создание список файлов в цикле 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 # Удаление файлов, начинающихся с "j" или "x" в $PWD. echo "Удален файл \"$file\"". done echo exit 0Если [список] в цикле for не задан, то в качестве оного используется переменная $@ -- список аргументов командной строки. Оень остроумно эта особенность проиллюстрирована в Пример A-18.
Пример 10-5. Цикл for без списка аргументов
#!/bin/bash # Попробуйте вызвать этот сценарий с аргументами и без них и посмотреть на результаты. for a do echo -n "$a " done # Список аргументов не задан, поэтому цикл работает с переменной '$@' #+ (список аргументов командной строки, включая пробельные символы). echo exit 0При создании списка аргументов, в цикле for допускается пользоваться подстановкой команд. См. Пример 12-39, Пример 10-10 и Пример 12-33.
Пример 10-6. Создание списка аргументов в цикле for с помощью операции подстановки команд
#!/bin/bash # уЩЫЬ for гЯ [гаЩгЫЯЭ], гЯкФСЮЮйЭ г аЯЭЯниР аЯФгдСЮЯзЫЩ ЫЯЭСЮФ. NUMBERS="9 7 3 8 37.53" for number in `echo $NUMBERS` # for number in 9 7 3 8 37.53 do echo -n "$number " done echo exit 0Более сложный пример использования подстановки команд при создании списка аргументов цикла.
Пример 10-7. grep для бинарных файлов
#!/bin/bash # bin-grep.sh: Поиск строк в двоичных файлах. # замена "grep" для бинарных файлов. # Аналогично команде "grep -a" E_BADARGS=65 E_NOFILE=66 if [ $# -ne 2 ] then echo "Порядок использования: `basename $0` string filename" exit $E_BADARGS fi if [ ! -f "$2" ] then echo "Файл \"$2\" не найден." exit $E_NOFILE fi for word in $( strings "$2" | grep "$1" ) # Инструкция "strings" возвращает список строк в двоичных файлах. # Который затем передается по конвейеру команде "grep", для выполнения поиска. do echo $word done # Как указывает S.C., вышепрведенное объявление цикла for может быть упрощено # strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]' # Попробуйте что нибудь подобное: "./bin-grep.sh mem /bin/ls" exit 0Еще один пример.
Пример 10-8. Список всех пользователей системы
#!/bin/bash # userlist.sh PASSWORD_FILE=/etc/passwd n=1 # Число пользователей for name in $(awk 'BEGIN{FS=":"}{print $1}' < "$PASSWORD_FILE" ) # Разделитель полей = : ^^^^^^ # Вывод первого поля ^^^^^^^^ # Данные берутся из файла паролей ^^^^^^^^^^^^^^^^^ do echo "Пользователь #$n = $name" let "n += 1" done # Пользователь #1 = root # Пользователь #2 = bin # Пользователь #3 = daemon # ... # Пользователь #30 = bozo exit 0И заключительный пример использования подстановки команд при создании [списка].
Пример 10-9. Проверка авторства всех бинарных файлов в текущем каталоге
#!/bin/bash # findstring.sh: # Поиск заданной строки в двоичном файле. directory=/usr/local/bin/ fstring="Free Software Foundation" # Поиск файлов от FSF. for file in $( find $directory -type f -name '*' | sort ) do strings -f $file | grep "$fstring" | sed -e "s%$directory%%" # Команде "sed" передается выражение (ключ -e), #+ для того, чтобы изменить обычный разделитель "/" строки поиска и строки замены #+ поскольку "/" - один из отфильтровываемых символов. # Использование такого символа порождает сообщение об ошибке (попробуйте). done exit 0 # Упражнение: # --------------- # Измените сценарий таким образом, чтобы он брал #+ $directory и $fstring из командной строки.Результат работы цикла for может передаваться другим командам по конвейеру.
Пример 10-10. Список символических ссылок в каталоге
#!/bin/bash # symlinks.sh: Список символических ссылок в каталоге. directory=${1-`pwd`} # По-умолчанию в текущем каталоге, # Блок кода, который выполняет аналогичные действия. # ---------------------------------------------------------- # ARGS=1 # Ожидается один аргумент командной строки. # # if [ $# -ne "$ARGS" ] # Если каталог поиска не задан... # then # directory=`pwd` # текущий каталог # else # directory=$1 # fi # ---------------------------------------------------------- echo "символические ссылки в каталоге \"$directory\"" for file in "$( find $directory -type l )" # -type l = символические ссылки do echo "$file" done | sort # В противном случае получится неотсортированный список. # Как отмечает Dominik 'Aeneas' Schnitzer, #+ в случае отсутствия кавычек для $( find $directory -type l ) #+ сценарий "подавится" именами файлов, содержащими пробелы. exit 0Вывод цикла может быть перенаправлен со stdout в файл, ниже приводится немного модифицированный вариант предыдущего примера, демонстрирующий эту возможность.
Пример 10-11. Список символических ссылок в каталоге, сохраняемый в файле
#!/bin/bash # symlinks.sh: Список символических ссылок в каталоге. OUTFILE=symlinks.list # файл со списком directory=${1-`pwd`} # По-умолчанию -- текущий каталог, echo "символические ссылки в каталоге \"$directory\"" > "$OUTFILE" echo "---------------------------" >> "$OUTFILE" for file in "$( find $directory -type l )" # -type l = символические ссылки do echo "$file" done | sort >> "$OUTFILE" # перенаправление вывода # ^^^^^^^^^^^^^ в файл. exit 0Оператор цикла for имеет и альтернативный синтаксис записи -- очень похожий на синтаксис оператора for в языке C. Для этого используются двойные круглые скобки.
Пример 10-12. C-подобный синтаксис оператора цикла for
#!/bin/bash # Два вапианта оформления цикла. echo # Стандартный синтаксис. for a in 1 2 3 4 5 6 7 8 9 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См. так же Пример 25-10, Пример 25-11 и Пример A-7.
---
А сейчас пример сценария, который может найти "реальное" применение.
Пример 10-13. Работа с командой efax в пакетном режиме
#!/bin/bash EXPECTED_ARGS=2 E_BADARGS=65 if [ $# -ne $EXPECTED_ARGS ] # Проверка наличия аргументов командной строки. then echo "Порядок использования: `basename $0` phone# text-file" exit $E_BADARGS fi if [ ! -f "$2" ] then echo "Файл $2 не является текстовым файлом" exit $E_BADARGS fi fax make $2 # Создать fax-файлы из текстовых файлов. for file in $(ls $2.0*) # Все файлы, получившиеся в результате преобразования. # Используется шаблонный символ в списке. do fil="$fil $file" done efax -d /dev/ttyS3 -o1 -t "T$1" $fil # отправить. # Как указывает S.C., в цикл for может быть вставлена сама команда отправки в виде: # efax -d /dev/ttyS3 -o1 -t "T$1" $2.0* # но это не так поучительно [;-)]. exit 0
Оператор while проверяет условие перед началом каждой итерации и если условие истинно (если код возврата равен 0), то управление передается в тело цикла. В отличие от циклов for, циклы while используются в тех случаях, когда количество итераций заранее не известно.
while [condition]
do
command...
done
Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do.
while [condition] ; do
Обратите внимание: в отдельных случаях, таких как использование конструкции getopts совместно с оператором while, синтаксис несколько отличается от приводимого здесь.
Пример 10-14. Простой цикл while
#!/bin/bash var0=0 LIMIT=10 while [ "$var0" -lt "$LIMIT" ] do echo -n "$var0 " # -n подавляет перевод строки. var0=`expr $var0 + 1` # допускается var0=$(($var0+1)). done echo exit 0
Пример 10-15. Другой пример цикла while
#!/bin/bash echo while [ "$var1" != "end" ] # возможна замена на while test "$var1" != "end" do echo "Введите значение переменной #1 (end - выход) " read var1 # Конструкция 'read $var1' недопустима (почему?). echo "переменная #1 = $var1" # кавычки обязательны, потому что имеется символ "#". # Если введено слово 'end', то оно тоже выводится на экран. # потому, что проверка переменной выполняется в начале итерации (перед вводом). echo done exit 0
Оператор while может иметь несколько условий. Но только последнее из них определяет возможность продолжения цикла. В этом случае синтаксис оператора цикла должен быть несколько иным.
Пример 10-16. Цикл while с несколькими условиями
#!/bin/bash var1=unset previous=$var1 while echo "предыдущее значение = $previous" echo previous=$var1 # запомнить предыдущее значение [ "$var1" != end ] # В операторе "while" присутствуют 4 условия, но только последнее управляет циклом. # *последнее* условие - единственное, которое вычисляется. do echo "Введите значение переменной #1 (end - выход) " read var1 echo "текущее значение = $var1" done # попробуйте самостоятельно разобраться в сценарии works. exit 0
Как и в случае с for, цикл while может быть записан в C-подобной нотации, с использованием двойных круглых скобок (см. так же Пример 9-28).
Пример 10-17. C-подобный синтаксис оформления цикла while
#!/bin/bash # wh-loopc.sh: Цикл перебора от 1 до 10. LIMIT=10 a=1 while [ "$a" -le $LIMIT ] do echo -n "$a " let "a+=1" done # Пока ничего особенного. echo; echo # +=================================================================+ # А теперь оформим в стиле языка C. ((a = 1)) # a=1 # Двойные скобки допускают наличие лишних пробелов в выражениях. while (( a <= LIMIT )) # В двойных скобках символ "$" перед переменными опускается. do echo -n "$a " ((a += 1)) # let "a+=1" # Двойные скобки позволяют наращивание переменной в стиле языка C. done echo # Теперь, программисты, пишущие на C, могут чувствовать себя в Bash как дома. exit 0
Стандартное устройство ввода stdin, для цикла while, можно перенаправить на файл с помощью команды перенаправления < в конце цикла. |
Оператор цикла until проверяет условие в начале каждой итерации, но в отличие от while итерация возможна только в том случае, если условие ложно.
until [condition-is-true]
do
command...
done
Обратите внимание: оператор until проверяет условие завершения цикла ПЕРЕД очередной итерацией, а не после, как это принято в некоторых языках программирования.
Как и в случае с циклами for/in, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ ";" перед do.
until [condition-is-true] ; do