BASH: описание циклов for, while, until и примеры использования

Автор: | 11/15/2013
 

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

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 есть файлы для отображения – цикл будет передавать их в переменную и выполнять “тело цикла”. Как только список файлов в директории закончится – цикл завершит своё выполнение.

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

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

ls -1
file1
file2
file3
file4
file5
loop.sh
nofile1
nofile2
nofile3
nofile4
nofile5

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

#!/bin/bash

lsl=`ls -1`

for variable in $lsl
  do
    echo "$variable" | grep -v "no"
  done

Запускаем:

./loop.sh
file1
file2
file3
file4
file5
loop.sh

В цикле так же можно использовать условные выражения (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:

./loop.sh
file1
file2
file3
file4
file5

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

#!/bin/bash

for (( count=1; count<11; count++ ))
  do
    echo "$count"
  done

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

./loop.sh
1
2
3
4
5
6
7
8
9
10

Циклы WHILE и UNTIL

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

#!/bin/bash

count=0

while [ $count -lt 10 ]
  do
    (( count++ ))
    echo $count
  done

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

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

./loop.sh
1
2
3
4
5
6
7
8
9
10

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

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

#!/bin/bash

count=10

while [ 1 = 1 ]
  do
    (( count++ ))
    echo $count
  done

Запускаем:

./loop.sh
...
5378
5379
5380
5381
5382
5383
^C

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

#!/bin/bash

count=0

until [ $count -gt 10 ]
  do
    (( count++ ))
    echo $count
  done

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

./loop.sh
1
2
3
4
5
6
7
8
9
10
11

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

#!/bin/bash

count=10

until [ 1 = 1 ]
  do
    (( count++ ))
    echo $count
  done

Запускаем:

./loop.sh

Так как “условие” изначально “истинно” – тело цикла выполняться не будет.

Как и в цикле 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

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

./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

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

#!/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 – и завершился).