Краткое описание разницы в типах циклов:
for
– будет выполнять действие до тех пор, пока есть объекты для выполнения (например – чтение потока из stdin
, файла или функции);
while
– выполняет действие до тех пор, пока условие является истинным;
until
– будет выполняться до тех пор, пока условие не станет истинным, т.е. пока оно false
.
Содержание
Цикл FOR
Рассмотрим такой вариант скрипта с циклом:
#!/bin/bash for variable in `ls -1` do echo "$variable" done
Синтаксис очень простой и достаточно наглядно показан в примере:
for
(запускаем цикл) variable
(объявляем переменную, над которой будем выполнять действия) in
(направляем циклу поток) `ls -1`
(команда, которую необходимо выполнить и передать в переменную $variable
). Do
и done
– “тело” цикла, в рамках которых будут выполняться основные действия над полученными данными, а echo "$variable"
– непосредственно само действие, выполняемое циклом.
Теперь немного изменим пример, и вместо явного указания команды – применим вторую переменную:
#!/bin/bash ls=`ls -1` for variable in $ls do echo "$variable" done
Теперь команда ls -1
передаётся в отдельной переменной, что позволяет более гибко работать с циклом. Вместо переменной в цикле можно использовать и функцию:
#!/bin/bash lsl () { ls -1 } for variable in `lsl` do echo "$variable" done
Подробнее о функциях в статье BASH: использование функций, примеры.
Основное условие цикла for
– он будет выполняться до тех пор, пока в переданной ему команде есть объекты для действия. Исходя из примера выше – пока в листинге ls -1
есть файлы для отображения – цикл будет передавать их в переменную и выполнять “тело цикла”. Как только список файлов в директории закончится – цикл завершит своё выполнение.
Давайте немного усложним пример.
В каталоге имеется список файлов:
[simterm]
$ ls -1 file1 file2 file3 file4 file5 loop.sh nofile1 nofile2 nofile3 nofile4 nofile5
[/simterm]
Нам необходимо выбрать из них только те, которые в названии не имеют слова “no“:
#!/bin/bash lsl=`ls -1` for variable in $lsl do echo "$variable" | grep -v "no" done
Запускаем:
[simterm]
$ ./loop.sh file1 file2 file3 file4 file5 loop.sh
[/simterm]
В цикле так же можно использовать условные выражения (conditional expressions) для проверки условий и оператор break
для прерывания цикла в случае срабатывания условия.
Рассмотрим такой пример:
#!/bin/bash lsl=`ls -1` for variable in $lsl do if [ $variable != "loop.sh" ] then echo "$variable" | grep -v "no" else break fi done
Цикл будет выполняться до тех пор, пока не будет встречен файл loop.sh
. Как только выполнение цикла дойдёт до этого файла – цикл будет прерван командой break
:
[simterm]
$ ./loop.sh file1 file2 file3 file4 file5
[/simterm]
Ещё один пример – использование арифметических операций непосредственно перед выполнением тела цикла:
#!/bin/bash for (( count=1; count<11; count++ )) do echo "$count" done
Тут мы задаём три управляющих команды – count=1
, контролирующее условие – пока count
меньше 11, и команду для выполнения – count +1
:
[simterm]
$ ./loop.sh 1 2 3 4 5 6 7 8 9 10
[/simterm]
Циклы WHILE
и UNTIL
Простой пример, хорошо демонстрирующий принцип работы цикла while
:
#!/bin/bash count=0 while [ $count -lt 10 ] do (( count++ )) echo $count done
Мы задаём переменную $count
равной нулю, после чего запускаем цикл while с условием “пока
$count
меньше десяти – выполнять цикл”. В теле цикла мы выполняем постфиксный инкремент +1 к переменной $count
и результат выводим в stdout
.
Результат выполнения:
[simterm]
$ ./loop.sh 1 2 3 4 5 6 7 8 9 10
[/simterm]
Как только значение переменной $count
стало 10 – цикл прекратился.
Хороший пример “бесконечного” цикла, который демонстрирует работу while
:
#!/bin/bash count=10 while [ 1 = 1 ] do (( count++ )) echo $count done
Запускаем:
[simterm]
$ ./loop.sh ... 5378 5379 5380 5381 5382 5383 ^C
[/simterm]
Аналогично, но “в обратную сторону” работает и цикл until
:
#!/bin/bash count=0 until [ $count -gt 10 ] do (( count++ )) echo $count done
Тут мы задаём похожее условие, но вместо “пока переменная меньше 10” – указываем “пока переменная не станет больше чем 10”. Результат выполнения:
[simterm]
$ ./loop.sh 1 2 3 4 5 6 7 8 9 10 11
[/simterm]
Если же приведённый выше пример “бесконечного цикла” выполнить с использованием until
– о не выведет ничего, в отличии от while
:
#!/bin/bash count=10 until [ 1 = 1 ] do (( count++ )) echo $count done
Запускаем:
[simterm]
$ ./loop.sh $
[/simterm]
Так как “условие” изначально “истинно” – тело цикла выполняться не будет.
Как и в цикле for
– в while
и until
можно использовать функции. Для примера – цикл из реально использующегося скрипта, выполняющий проверку статуса сервера Tomcat (PID
берётся в системе SLES, в других системах может отличаться), немного упрощенный вариант:
#!/bin/bash check_tomcat_status () { RUN=`ps aux | grep tomcat | grep -v grep | grep java | awk '{print $2}'` } while check_tomcat_status do if [ -n "$RUN" ] then printf "WARNING: Tomcat still running with PID $RUN." else printf "Tomcat stopped, proceeding...nn" break fi done
Результат выполнения:
[simterm]
$ ./loop.sh WARNING: Tomcat still running with PID 14435 26548.WARNING: Tomcat still running with PID 14435 26548.WARNING: Tomcat still running with PID 14435 26548.WARNING: Tomcat still running with PID 14435 26548.WARNING: Tomcat still running with PID 14435 26548.WARNING: Tomcat still running with PID 14435 26548.WARNING: Tomcat still running with PID 14435 26548.WARNING: Tomcat still running with PID 14435
[/simterm]
Полный вариант:
#!/bin/bash check_tomcat_status () { RUN=`ps aux | grep tomcat | grep -v grep | grep java | awk '{print $2}'` } while check_tomcat_status; do if [ -n "$RUN" ] then printf "WARNING: Tomcat still running with PID $RUN. Stop it? " answer "Stopping Tomcat..." "Proceeding installation..." && $CATALINA_HOME/bin/shutdown.sh 2&>1 /dev/null || break sleep 2 if [ -n "$RUN" ] then printf "Tomcat still running. Kill it? " answer "Killing Tomcat..." "Proceeding installation...n" && kill $RUN || break sleep 2 fi else printf "Tomcat stopped, proceeding...nn" break fi done
Функция answer
описывалась в статье BASH: использование функций, примеры, но тут используется немного улучшенный вариант:
answer () { while read response; do echo case $response in [yY][eE][sS]|[yY]) printf "$1n" return 0 break ;; [nN][oO]|[nN]) printf "$2n" return 1 break ;; *) printf "Please, enter Y(yes) or N(no)! " esac done }
Тут можно было использовать как while
, так и until
– но не цикл for,
так как for
сработал бы один раз (получил PID
– и завершился).