При составлении скриптов часто требуется повторять последовательность действий с объектами, имеющими общие признаки (расположение, имя, размер), либо выполнять такие действия, пока истинно конкретное условие.
В языке bash, как и в большинстве языков программирования, для этих целей существует управляющая конструкция — цикл.
В статье рассмотрим все три типа циклов bash: for
, while
и until
, а также команды для управления ходом цикла — break
и continue
.
Цикл for
Этот вид цикла используют чаще всего. С его помощью можно несколько раз выполнить один и тот же набор действий с объектами внутри массива, определённого тем или иным способом — примеры даны в таблице ниже.
Синтаксис цикла for
в общем виде:
for переменная in массив
do
команда 1
команда 2
команда n
done
Слово массив выше обозначает любой объект, по которому цикл может совершать итерации (обходить объект) и который представляет собой массив (строки и/или числа) либо последовательность (только числа) или указывает его расположение:
Объект обхода и синтаксис | Пример команды или скрипта | Пояснение |
---|---|---|
Cлова, разделённые пробелами
|
Результат имеет желаемый вид:
| Чтобы оператор for распознал слова как самостоятельные элементы массива, между ними должны быть пробелы. Группа слов, заключённая в кавычки, рассматривается как один элемент. |
Массивы
|
Выводимый результат:
| Также слова можно включать в массивы. Как и в случае выше, группа слов, заключённая в кавычки, рассматривается как один элемент массива. |
Вывод команд
|
Выводимый результат:
| Скрипт выполняет с каждой строкой файла
|
Пути к файлам
|
| Скрипт проверяет резервные копии (архивы файлов формата .gz ) в каталоге на наличие повреждений, удаляет повреждённые архивы и выводит сообщение о каждом удалённом архиве. |
Последовательности чисел
|
| Скрипт возводит числа указанной последовательности в квадрат. |
Цикл while
Цикл while требует, чтобы указанный код продолжал выполняться, пока условие истинно.
В общем виде синтаксис цикла while
выглядит так:
while [ условие ]; do
команда 1
команда 2
done
Вот пример использования цикла while для работы таймера:
#!/bin/bash
echo "Введите количество секунд для отсчёта: "
read duration
while [ $duration -gt 0 ]; do
echo -ne "Осталось секунд: $duration\r"
sleep 1
((duration--))
done
echo -e "\nВремя вышло!"
Скрипт выполняет следующие действия:
- Пользователю предлагают ввести значение переменной
duration
— число секунд для отсчёта. - Пользователь вводит значение, и цикл запускается.
- Согласно условию
[ $duration -gt 0 ]
, пока переменнаяduration
больше нуля, на экране отображается надпись:
Осталось секунд: <текущее значение переменной duration>
Затем курсор перемещается в начало этой же строки (\r
— литерал (сочетание спецсимволов) для возврата к началу строки).
- Команда
sleep 1
вызывает бездействие оболочки на одну секунду. - Количество секунд уменьшается на единицу.
- При новом проходе цикла надпись с изменённым количеством секунд отображается на той же строке.
- Когда переменная
duration
становится равна нулю, на новой строке (\n
— литерал новой строки) отображается надпись:
Время вышло!
Цикл until
Цикл until
продолжает работу, пока условие ложно.
В общем виде синтаксис цикла until
выглядит так:
until [ условие ]; do
команда 1
команда 2
done
Вот версия таймера с использованием этого вида цикла:
#!/bin/bash
echo "Введите количество секунд для отсчёта:"
read duration
until [ $duration -eq 0 ]; do
echo -ne "Осталось секунд: $duration\r"
sleep 1
((duration--))
done
echo -e "\nВремя вышло!"
Скрипт работает почти так же, как в примере для цикла while. Различие — в пункте 3: согласно условию [ $duration -eq 0 ]
, пока переменная duration
не станет равна нулю, на экране отображается надпись:
Осталось <текущее значение переменной duration> секунд.
Затем курсор перемещается в начало этой же строки.
Управление ходом выполнения цикла
Команды break
и continue
позволяют менять ход цикла:
break
прерывает цикл;continue
позволяет прервать текущую итерацию и начать следующую.
Команда break
Команда break
используется для выхода из любых циклов. Для первых трёх типов цикла этот приём можно применять, когда нельзя предсказать, сколько раз будет выполняться цикл — например, если число итераций зависит от введённого пользователем значения.
Если команда break
не имеет аргументов, она прерывает выполнение того цикла, в котором присутствует. Если ей передать аргумент:
break [n]
будет прерван цикл, расположенный на n-1
уровней выше текущего, так как n
текущего уровня — 1.
Следует помнить, что break
завершает цикл, а не скрипт. Это можно проверить, добавив в конец скрипта команду echo
.
Общий вид синтаксиса:
for [ условие ]; do
команда 1
команда 2
if [ condition ]; then
break
fi
команда3
done
Команда continue
Команда continue
позволяет прервать текущую итерацию цикла: эту команду включают в цикл после условия, при выполнении которого текущая итерация цикла завершается и начинается следующая. Эта команда работает во всех типах циклов.
Общий вид синтаксиса:
for [ условие ]; do
if [ условие 2 ]
then
continue
fi
команда
done
Как правило, предварительная фильтрация данных вне цикла и последующая их обработка в цикле выполняются быстрее, чем проверка условия внутри цикла и прерывание итерации при помощи continue
.
Но в некоторых случаях вариант с применением continue разумнее — например, если в скрипте используется файл, уже открытый другим процессом. Если в файл в данный момент ведётся запись, его обработка может привести к возникновению ошибок сжатия.
В следующем скрипте цикл сначала проверяет, закрыт ли файл резервной копии (дампа) базы данных, и если закрыт, то архивирует его и перемещает в каталог с архивами.
#!/bin/bash
backup_dir="/opt/mysql/backups"
archive_dir="/opt/mysql/backup-archives"
mkdir -p "$backup_dir" "$archive_dir"
for dump in "$backup_dir"/*.dump "$backup_dir"/*.sql; do
lsof "$dump" &>/dev/null && continue
gzip "$dump"
mv "$dump.gz" "$archive_dir"
done
Скрипт выполняет следующие действия:
- В переменную
backup_dir
записывается путь к каталогу c дампами БД, в переменнуюarchive_dir
— путь к каталогу с архивами. - Если указанные каталоги не существуют, они создаются командой
mkdir
. - Цикл
for
обходит в каталоге, обозначенном переменной backup_dir, все файлы с именем, заканчивающимся на.sql
или.dump
. Обработка каждого элемента обозначается итерацией с именемdump
.
В ходе каждой итерации совершаются следующие действия:
3.1. Команда lsof
проверяет, находится ли файл в списке открытых. Если файл открыт, то итерация с ним прерывается и цикл переходит к обработке следующего файла с именем, заканчивающимся на .sql
или .dump
.
3.2. Если файла нет в списке открытых, то он архивируется командой gzip, и к исходному имени добавляется расширение .gz. Исходный файл дампа удаляется.
3.3. Команда mv перемещает созданный архив в каталог, обозначенный переменной archive_dir
.
- Цикл завершается.
Вложенные циклы
Вложенные циклы используют для повторения набора действий с элементами на нескольких уровнях структур данных или последовательностей. Путём обращения к элементам массивов и древовидных структур, таких как каталоги или json-файлы среднего размера, можно вести поиск, фильтрацию и сортировку этих элементов.
Для работы с большими файлами или несколькими уровнями вложенности лучше использовать специализированные инструменты — например, Python и его библиотеки simplejson
, ujson
, pandas
.
Вложенные циклы могут быть любых типов — for
, while
или until
. Чаще всего встречаются вложенные циклы for.
Во вложенном цикле for
внешний цикл управляет обходом первого массива данных, а внутренний цикл для каждого элемента массива во внешнем цикле обходит второй массив данных.
Общий вид синтаксиса цикла for:
for [ условие 1 ]; do
for[ условие 2 ]; do
команда 1
done
команда 2
done
Мы рассмотрели типы циклов bash: for
, while
и until
, а также способы управления циклами — команды break
и continue
.
Циклы — одна из основных управляющих конструкций в программировании. При автоматизации повторяющихся задач без них часто не обойтись. Важно знать, для каких целей лучше подходят разные виды циклов.
Автор статьи: Клара Суботэ