Переклад поста 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`– команда, яку необхідно виконати і передати в змінну$variabledoі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 і завершився).