BASH: використання циклів FOR, WHILE, UNTIL – приклади

Автор |  15/01/2023
 

terminalПереклад поста 2013 року з деякими правками, але все ще актуальний для вивчення BASH.

Короткий опис різниці у типах циклів:

  • for – виконуватиме дію доти, доки є об’єкти для виконання (наприклад – читання потоку з stdin, файлу або функції);
  • while – виконує дію доти, доки умова є істинною;
  • until – виконуватиметься до того часу, поки умова стане true, тобто, поки вона false.

Цикл FOR

Розглянемо такий варіант скрипту із циклом 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

Простий приклад, що добре демонструє принцип роботи циклу 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 – цикл прервався.

Infinite loops

Гарний приклад “нескінченного” циклу, який демонструє роботу 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

Аналогічно, але “у зворотний бік” працює і цикл 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]

Оскільки “умова” початково “true” – тіло циклу виконуватися не буде.

Як і в циклі 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 і завершився).