Краткое описание разницы в типах циклов:
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 — и завершился).