Что повторять?
—Шекспир, Отелло
Операции с блоками кода — ключ к структурированным и упорядоченным сценариям оболочки. Циклы и ветвления являются теми инструментальными средствами, которые предоставляют возможность достигнуть этой цели.
Циклы
Цикл это блок команд, который повторяется (итерируется) [46] до тех пор, пока не будет выполнено условие выхода из цикла .
Цикл for
- for
argin[list] - Это одна из основных разновидностей циклов. И она значительно отличается от аналога в языке C.
forargin [list]
do
команда(ы)…
doneЗамечание
На каждом проходе цикла, переменная-аргумент цикла
argпоследовательно, одно за другим, принимает значения из спискаlist.for arg in "$var1" "$var2" "$var3" ... "$varN" # На первом проходе, arg = $var1 # На втором проходе, arg = $var2 # На третьем проходе, arg = $var3 # ... # На N-ном проходе, arg = $varN # Элементы списка [list] заключены в кавычки для того, чтобы предотвратить возможное разбиение их на отдельные аргументы (слова).
Элементы
спискамогут включать шаблонные символы.Если ключевое слово do находится в одной строке со словом for, то после списка аргументов (перед do) необходимо ставить точку с запятой.
forargin [list] ; doПример 1. Простой цикл for
#!/bin/bash # Список планет. for planet in Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон do echo $planet # Каждая планета на отдельной строке. done echo; echo for planet in "Меркурий Венера Земля Марс Юпитер Сатурн Уран Нептун Плутон" # Все планеты в одной строке. # Если 'список аргументов' заключить в кавычки, то он будет восприниматься как единственный аргумент. do echo $planet done echo; echo "Упс! Плутон больше не планета!" #См. здесь (прим.перев.) exit 0Каждый из элементов списка
[list]может содержать несколько аргументов. Это бывает полезным при обработке групп параметров. В этом случае, для принудительного разбора каждого из аргументов в списке[list]и присваивания каждого компонента позиционным параметрам, необходимо использовать команду setПример 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Переменная может замещать список
[list]в цикле for.Пример 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 не существует."; echo continue # Переход к следующей итерации. fi ls -l $file | awk '{ print $8 " размер файла: " $5 }' # Печать 2 полей. whatis `basename $file` # Информация о файле. # Заметим, что нужно создать индекс базы данных для whatis для того, чтобы предыдущая команда работала. # Чтобы сделать это, из-под пользователя root запустите /usr/bin/makewhatis. # (Для Debian/Ubuntu используйте команду /usr/bin/mandb - примеч. перев.) echo done exit 0Если список
[list]цикла for содержит шаблонные символы (* и ?), используемые в раскрытии имён файлов (pathname expansion), то имеет место процесс, называемый глоббинг (globbing)Пример 4. Обработка списка файлов в цикле for
#!/bin/bash # list-glob.sh: Создание списка файлов в цикле for с использованием # "глоббинга". echo for file in * # ^ Bash выполняет раскрытие имён файлов #+ в выражениях, которые являются результатом глоббинга. do ls -l "$file" # Отсортированный список всех файлов в $PWD (текущем каталоге). # # Напоминаю, что символу "*" соответствует любое имя файла, # однако, в для глоббинга # имеется исключение - имена файлов, начинающиеся с точки. # (Такие файлы по умолчанию не отображаются в списке совпадающих с шаблоном имен файлов, см. man bash. Спасибо Dr.Batty за это замечание - прим. перев.) # Если шаблону не соответствует ни один файл, то за имя файла принимается сам шаблон. # Чтобы избежать этого, используйте ключ 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
Если список
[list]в цикле for не задан, то в качестве оного используется переменная $@ — список всех аргументов командной строки (позиционных параметров). Очень остроумно эта особенность проиллюстрирована в Пример A.15, «Generating prime numbers using the modulo operator».Пример 5. Цикл for без списка аргументов
#!/bin/bash # Попробуйте вызвать этот сценарий с аргументами и без них и посмотреть на результаты. for a do echo -n "$a " done # Список аргументов не задан, поэтому цикл работает с переменной '$@' #+ (список аргументов командной строки, включая пробельные символы). echo exit 0
Также имеется возможность использования подстановки команд, чтобы сгенерировать список
[list]для цикла for. См. также Пример 10.Пример 6. Создание списка аргументов в цикле for с помощью подстановки команд
#!/bin/bash # for-loopcmd.sh: Цикл 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
Более сложный пример использования подстановки команд при создании списка аргументов цикла.Пример 7. grep для бинарных файлов
#!/bin/bash # bin-grep.sh: Вывод строк, содержащих указанную подстроку в бинарном файле. # Замена "grep" для бинарных файлов. # Аналогична команде "grep -a" E_BADARGS=65 E_NOFILE=66 if [ $# -ne 2 ] then echo "Использование: `basename $0` искомая_строка имя_файла" exit $E_BADARGS fi if [ ! -f "$2" ] then echo "Файл \"$2\" не существует." exit $E_NOFILE fi IFS=$'\012' # С подачи Антона Филиппова (23 строка) # было: IFS="\n" for word in $( strings "$2" | grep "$1" ) # Команда "strings" возвращает список строк в двоичных файлах. # Который затем передается по конвейеру команде "grep" для выполнения поиска. do echo $word done # (30 строка) # Как указывает S.C., строки 23 - 30 могут быть заменены более простым # strings "$2" | grep "$1" | tr -s "$IFS" '[\n*]' # Попробуйте проверить работу этого скрипта с помощью чего-то наподобие "./bin-grep.sh mem /bin/ls" exit 0Пример 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 # Вопрос: # -------- # Что вы думаете о том, что обыкновенный пользователь (или запускаемый скрипт с правами этого пользователя) #+ может читать из файла /etc/passwd? # Не является ли это дырой в безопасности? Почему да либо почему нет?
Еще пример с подстановкой команд.Пример 9. Проверка авторства всех бинарных файлов в каталоге
#!/bin/bash # findstring.sh: # Поиск заданной строки в двоичном файле в определенном каталоге. directory=/usr/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, #+ необходимо заменить обычный разделитель "/", #+ так как "/" встречается среди отфильтровываемых символов. # Использование такого символа порождает сообщение об ошибке (попробуйте). 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 может передаваться другим командам по конвейеру.
Пример 10. Список символических ссылок в каталоге.
#!/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" строка раскрывается в одиночное слово. # Тем не менее, этот способ легко понять и проиллюстрировать. # Как отмечает Доминик 'Aeneas' Шнитцер, #+ в случае отсутствия кавычек для $( find $directory -type l ) #+ сценарий перестанет выполняться, как только встретится файл, содержащий пробел в имени. # Сценарий "подавится" именами файлов, содержащими пробел. exit 0 # -------------------------------------------------------- # Жан Хелу предлагает следующую альтернативу: 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 # И, наконец, Джеймс "Mike" Конли предложил изменить код Хелу таким образом: OLDIFS=$IFS IFS='' # Пустое значение IFS означает, что нет разрыва между словами for file in $( find $directory -type l ) do echo $file done | sort # Это работает в "патологическом" случае - если имя каталога содержит #+ двоеточие. # "Это также исправляет патологический случай имени каталога, содержащего #+ двоеточие (или пробел в примере выше)."Вывод из
stdoutцикла может быть перенаправлен в файл, ниже приводится немного модифицированный вариант предыдущего примера, демонстрирующий эту возможность.Пример 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. Для этого используются двойные круглые скобки.
Пример 12. C-подобный синтаксис оператора цикла for
#!/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 # +==========================================+ # Используя раскрытие скобок (brace expansion) ... # Требуется 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А теперь сценарий с использованием цикла for из «реальной жизни».
Пример 13. Использование команды efax в пакетном режиме
#!/bin/bash # Отправка факса (пакет 'efax' должен быть установлен). EXPECTED_ARGS=2 E_BADARGS=85 MODEM_PORT="/dev/ttyS2" # Порт может быть другим на вашей машине. # ^^^^^ Порт по умолчанию PCMCIA-модема. if [ $# -ne $EXPECTED_ARGS ] # Проверка необходимого количества аргументов командной строки. then echo "Использование: `basename $0` телефон# текстовый файл" exit $E_BADARGS fi if [ ! -f "$2" ] then echo "Файл $2 не является текстовым файлом." # Не обычный (regular) файл, либо файл не существует. 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-loop с помощью # efax -d /dev/ttyS2 -o1 -t "T$1" $2.0* #+ но это не так поучительно [;-)]. exit $? # efax также отправляет диагностические сообщения на стандартный вывод (stdout). - while
- Оператор while проверяет условие перед началом каждой итерации и если условие истинно (если код возврата равен 0), то управление передается в тело цикла. В отличие от цикла for, цикл while используется в тех случаях, когда количество итераций заранее не известно.
while[условие]
do
команда(-ы)…
doneКонструкция с квадратными скобками в цикле while ничего более, чем наши старая знакомая — команда, используемая для проверки условия if/then. На самом деле цикл while вполне может использовать более универсальную конструкцию с двойными квадратными скобками (while [[ условие ]]).
Как и в случае с циклом for, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ «;» перед do:
while[условие] ; doОбратите внимание: квадратные скобки не являются обязательными для цикла while. Для примера, см. конструкцию с использованием команды getopts .
Пример 14. Простой цикл while
#!/bin/bash var0=0 LIMIT=10 while [ "$var0" -lt "$LIMIT" ] # ^ ^ # Пробелы, так как используются квадратные скобки - аналог команды test . . . do echo -n "$var0 " # -n подавляет перевод строки. # ^ Пробел, чтобы разделить выводимые числа. var0=`expr $var0 + 1` # var0=$(($var0+1)) также допустимо. # var0=$((var0 + 1)) также допустимо. # let "var0 += 1" также допустимо. done # Различные допустимые варианты. echo exit 0Пример 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 может иметь несколько условий. Но только последнее из них определяет возможность продолжения выполнения цикла. Это требует несколько другого синтаксиса.
Пример 16. Цикл while loop с несколькими условиями
#!/bin/bash var1=unset previous=$var1 while echo "предыдущее значение = $previous" echo previous=$var1 [ "$var1" != end ] # Следить за тем, какое значение $var1 было ранее. # В операторе "while" присутствуют 4 условия, но только последнее определяет возможность продолжения цикла. # Код возврата *последнего* выражения - единственное значение, которое принимается во внимание. do echo "Введите значение переменной #1 (end - выход) " read var1 echo "текущее значение #1 = $var1" done # Попробуйте разобраться, как это все работает. # Сценарий немножко каверзный. exit 0Как и в случае с for, для цикла while может использоваться C-подобный синтаксис с использованием двойных круглых скобок
Пример 17. C-подобный синтаксис оформления цикла while
#!/bin/bash # wh-loopc.sh: Считаем до 10, используя цикл "while". LIMIT=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 могут чувствовать себя в 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 "Цикл все еще выполняется: t = $t" done # Цикл все еще выполняется: t = 1 # Цикл все еще выполняется: t = 2 # Цикл все еще выполняется: t = 3 # Цикл все еще выполняется: t = 4Как и для конструкции if-test , в подобных случаях для цикла while скобки в условии можно опустить.
while условие do команда(ы) ... done
Соединяя мощь команды read с циклом while, мы получаем удобную конструкцию while-read, полезную для чтения и синтаксического разбора файлов.
cat $filename | # Построчный вывод из файла. while read line # Продолжать до тех пор, пока есть следующая строка для чтения . . . do ... done # =========== Фрагмент из скрипта-примера "sd.sh" ========== # while read value # Читать одну строку с данными за один раз. do rt=$(echo "шкала=$SC; $rt + $value" | bc) (( ct++ )) done am=$(echo "шкала=$SC; $rt / $ct" | bc) echo $am; return $ct # Эта функция "возвращает" ДВА значения! # Внимание: Эта маленькая уловка не будет работать, если $ct > 255! # Чтобы обрабатывать большее число результатов обработки данных, #+ просто закомментируйте строку "return $ct" выше. } <"$datafile" # Получение данных из файла.Замечание
Цикл while может перенаправить данные из файла на свой
стандартный ввод (stdin)с помощью символа < в конце цикла.Цикл while может получать данные через конвейер (pipe) на свой
стандартный ввод (stdin). - until
- Эта конструкция проверяет условие в начале цикла и выполняет итерации, пока условие ложно (в отличие от цикла while).
until[условие]
do
команда(ы)…
doneОбратите внимание: оператор цикла until проверяет условие завершения цикла перед очередной итерацией, а не после, как это принято в некоторых языках программирования.
Как и в случае с циклом for, при размещении ключевого слова do в одной строке с объявлением цикла, необходимо вставлять символ «;» перед do.
Пример 18. until loop
#!/bin/bash END_CONDITION=end until [ "$var1" = "$END_CONDITION" ] # Условие проверяется здесь, перед началом итерации. do echo "Введите значение переменной #1 " echo "($END_CONDITION - выход)" read var1 echo "значение переменной #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, однако, ситуация не четко определена . В Bash цикл for — более свободно структурирован и более гибок, чем его эквиваленты в других языках. Поэтому не стесняйтесь использовать любой тип цикла, выполняющий свою задачу самым простым способом.
Оригинал : bash-scripting.ru/abs/chunks/ch10.html
RSS & RSS to Email