FAQ

BASH: описание циклов for, while, until

Краткое описание разницы в типах циклов:

for — будет выполнять действие до тех пор, пока есть объекты для выполнения (например — чтение потока из stdin, файла или функции);
while — выполняет действие до тех пор, пока условие является истинным;
until — будет выполняться до тех пор, пока условие не станет истинным, т.е. пока оно false.

Цикл FOR

Рассмотрим такой вариант скрипта с циклом:

$ cat loop.sh#!/bin/bashfor variable in `ls -1`doecho "$variable"done

Синтаксис очень простой и достаточно наглядно показан в примере:

for (запускаем цикл) variable (объявляем переменную, над которой будем выполнять действия) in (направляем циклу поток) `ls -1` (команда, которую необходимо выполнить и передать в переменную $variable). Do и done — «тело» цикла, в рамках которых будут выполняться основные действия над полученными данными, а echo "$variable" — непосредственно само действие, выполняемое циклом.

Теперь немного изменим пример, и вместо явного указания команды — применим вторую переменную:

$ cat loop.sh#!/bin/bashls=`ls -1`for variable in $lsdoecho "$variable"done

Теперь команда ls -1 передаётся в отдельной переменной, что позволяет более гибко работать с циклом. Вместо переменной в цикле можно использовать и функцию:

$ cat loop.sh#!/bin/bashlsl () {ls -1}for variable in `lsl`doecho "$variable"done

Основное условие цикла for — он будет выполняться до тех пор, пока в переданной ему команде есть объекты для действия. Исходя из примера выше — пока в листинге ls -1 есть файлы для отображения — цикл будет передавать их в переменную и выполнять «тело цикла». Как только список файлов в директории закончится — цикл завершит своё выполнение.

Давайте немного усложним пример.

В каталоге имеется список файлов:

$ ls -1file1file2file3file4file5loop.shnofile1nofile2nofile3nofile4nofile5

Нам необходимо выбрать из них только те, которые в названии не имеют слова «no«:

$ cat loop.sh#!/bin/bashlsl=`ls -1`for variable in $lsldoecho "$variable" | grep -v "no"done

$ ./loop.shfile1file2file3file4file5loop.sh

В цикле так же можно использовать условные выражения (conditional expressions) […] для проверки условий и оператор break для прерывания цикла в случае срабатывания условия.

Рассмотрим такой пример:

$ cat loop.sh#!/bin/bashlsl=`ls -1`for variable in $lsldoif [ $variable != "loop.sh" ]thenecho "$variable" | grep -v "no"elsebreakfidone

Цикл будет выполняться до тех пор, пока не будет встречен файл loop.sh. Как только выполнение цикла дойдёт до этого файла — цикл будет прерван командой break:

$ ./loop.shfile1file2file3file4file5

Ещё один пример — использование арифметических операций непосредственно перед выполнением тела цикла:

$ cat loop.sh#!/bin/bashfor (( count=1; count<11; count++ ))doecho "$count"done

Тут мы задаём три управляющих команды — count=1, контролирующее условие — пока countменьше 11, и команду для выполнения — count +1:

$ ./loop.sh12345678910

Циклы WHILE и UNTIL

Простой пример, хорошо демонстрирующий принцип работы цикла while:

$ cat loop.sh#!/bin/bashcount=0while [ $count -lt 10 ]do(( count++ ))echo $countdone

Мы задаём переменную $count равной нулю, после чего запускаем цикл while с условием «пока $count меньше десяти — выполнять цикл». В теле цикла мы выполняем постфиксный инкремент+1 к переменной $count и результат выводим в stdout.

Результат выполнения:

$ ./loop.sh12345678910

Как только значение переменной $count стало 10 — цикл прекратился.

Хороший пример «бесконечного» цикла, который демонстрирует работу while:

$ cat loop.sh#!/bin/bashcount=10while [ 1 = 1 ]do(( count++ ))echo $countdone

$ ./loop.sh...537853795380538153825383^C

Аналогично, но «в обратную сторону» работает и цикл until:

$ cat loop.sh#!/bin/bashcount=0until [ $count -gt 10 ]do(( count++ ))echo $countdone

Тут мы задаём похожее условие, но вместо «пока переменная меньше 10» — указываем «пока переменная не станет больше чем 10». Результат выполнения:

$ ./loop.sh1234567891011

Если же приведённый выше пример «бесконечного цикла» выполнить с использованием until — о не выведет ничего, в отличии от while:

$ cat loop.sh#!/bin/bashcount=10until [ 1 = 1 ]do        (( count++ ))        echo $countdone

Так как «условие» изначально «истинно» — тело цикла выполняться не будет.

Как и в цикле for — в while и until можно использовать функции. Для примера — цикл из реально использующегося скрипта, выполняющий проверку статуса сервера Tomcat (PID берётся в системе SLES, в других системах может отличаться), немного упрощенный вариант:

$ cat loop.sh#!/bin/bashcheck_tomcat_status () {RUN=`ps aux | grep tomcat | grep -v grep | grep java | awk '{print $2}'`}while check_tomcat_statusdo        if [ -n "$RUN" ]        then                printf "WARNING: Tomcat still running with PID $RUN."        else                printf "Tomcat stopped, proceeding...nn"        break        fidone

Результат выполнения:

$ ./loop.shWARNING: Tomcat still running with PID 1443526548.WARNING: Tomcat still running with PID 1443526548.WARNING: Tomcat still running with PID 1443526548.WARNING: Tomcat still running with PID 1443526548.WARNING: Tomcat still running with PID 1443526548.WARNING: Tomcat still running with PID 1443526548.WARNING: Tomcat still running with PID 1443526548.WARNING: Tomcat still running with PID 14435

Полный вариант:

check_tomcat_status () {RUN=`ps aux | grep tomcat | grep -v grep | grep java | awk '{print $2}'`}while check_tomcat_status; doif [ -n "$RUN" ]thenprintf "WARNING: Tomcat still running with PID $RUN. Stop it? "answer "Stopping Tomcat..." "Proceeding installation..." && $CATALINA_HOME/bin/shutdown.sh 2&>1 /dev/null || breaksleep 2if [ -n "$RUN" ]thenprintf "Tomcat still running. Kill it? "answer "Killing Tomcat..." "Proceeding installation...n" && kill $RUN || breaksleep 2fielseprintf "Tomcat stopped, proceeding...nn"breakfidone

Функция answer 

answer () {while read response; doechocase $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)! "esacdone}

Тут можно было использовать как while, так и until — но не цикл for,  так как for сработал бы один раз (получил PID — и завершился).