Условные операторы и циклы в Bash
Условные операторы и циклы в Bash
Основы логики управления потоком
Bash — это язык командной оболочки, который обеспечивает взаимодействие пользователя с операционной системой. Его главная сила заключается не только в выполнении отдельных команд, но и в возможности создавать сложные сценарии автоматизации. Эти сценарии называют скриптами. Для того чтобы скрипт был умным и мог реагировать на изменения окружающей среды, он должен обладать способностью принимать решения и повторять действия. Именно для этих целей в Bash используются условные операторы и циклы.
Условный оператор представляет собой механизм ветвления потока выполнения программы. Программа встречает условие, анализирует его истинность или ложность и выбирает один из нескольких возможных путей развития событий. Если условие истинно, выполняется один блок инструкций. Если условие ложно, программа переходит к альтернативному блоку или пропускает выполнение вовсе. Это позволяет скрипту обрабатывать разные ситуации: проверять наличие файлов, определять права доступа пользователя, анализировать результаты предыдущих команд и действовать соответственно.
Цикл представляет собой механизм повторения. Он позволяет выполнить одну и ту же последовательность действий многократно до тех пор, пока выполняется определенное условие или пока не будет достигнуто заданное количество итераций. Циклы критически важны для обработки списков файлов, проверки состояния системы в течение длительного времени, опроса пользователей или ожидания завершения внешних процессов. Без циклов скрипт был бы ограничен линейным выполнением команды за командой, что сделало бы невозможным решение большинства реальных задач автоматизации.
В Bash логика работы строится на понятии «выходного кода» (exit code). Каждая команда, которая выполняется в системе, возвращает числовой результат. Ноль (0) означает успешное завершение. Любое другое число (от 1 до 255) указывает на ошибку или специфическое состояние. Условные операторы и тестовые конструкции в Bash опираются именно на эти значения. Они проверяют, вернула ли команда ноль или нет, и на основе этого принимают решение о дальнейших действиях.
Что такое выходной код и как его интерпретировать
Понимание механизма возврата кодов является фундаментом для работы с условиями в Bash. В отличие от многих языков программирования, где условия часто выражаются через булевы значения true и false, Bash работает с числами. Команда считается «истинной», если она завершилась с кодом 0. Команда считается «ложной», если она завершилась с любым другим кодом.
Это поведение пронизывает всю систему. Когда вы пишете конструкцию if, Bash выполняет команду внутри скобок. Если эта команда возвращает 0, блок кода после then выполняется. Если команда возвращает ненулевой код, выполняется блок else (если он указан).
Примером может служить проверка существования файла. Команда test -f /etc/passwd проверяет, существует ли файл /etc/passwd. Если файл существует, команда возвращает 0. Если файла нет, возвращается 1. Скрипт использует этот результат для принятия решения: создать файл, прочитать его содержимое или вывести сообщение об ошибке.
Важно понимать, что в Bash нет встроенных типов данных true и false в привычном понимании C или Python. Вместо них существуют специальные команды true и false. Команда true всегда возвращает код 0, а команда false всегда возвращает код 1. Эти команды часто используются в тестах для принудительной установки состояния истинности или ложности.
#!/bin/bash
# Команда true всегда возвращает 0 (истина)
if true; then
echo "Этот текст будет напечатан, так как условие истинно"
fi
# Команда false всегда возвращает 1 (ложь)
if false; then
echo "Этот текст никогда не напечатается"
else
echo "Так как условие ложно, выполнится блок else"
fi
В этом примере мы видим прямую работу с концепцией выхода. Конструкция if true всегда проходит проверку, потому что внутренняя команда true возвращает 0. Конструкция if false всегда попадает в блок else, потому что команда false возвращает 1.
При написании сложных скриптов часто возникает необходимость проверить результат выполнения предыдущей команды без явного присваивания переменной. Для этого используется специальный символ $?. Он содержит выходной код последней выполненной команды.
ls /nonexistent_directory
echo $?
В данном случае команда ls попытается открыть несуществующую директорию. Она завершится с ошибкой, и переменная $? примет значение 1 (или другое ненулевое число). Скрипт может использовать это значение для анализа ситуации.
Существует также понятие «логического оператора» в контексте цепочек команд. Оператор && (амперсанд-амперсанд) выполняет вторую команду только если первая вернула 0. Оператор || (вертикальная черта-вертикальная черта) выполняет вторую команду только если первая вернула ненулевой код. Это позволяет писать компактные условия без использования многострочных конструкций if.
# Выполнить команду 'mkdir', если директория еще не создана
# mkdir вернет 0, если создала, или 1, если уже существовала
mkdir /tmp/mydir || echo "Директория уже существует"
# Выполнить команду 'rm', если файл существует
[ -f file.txt ] && rm file.txt
В первой строке, если mkdir успешно создает директорию (код 0), команда echo не выполняется. Если же директорию нельзя создать (код 1), выполняется echo. Во второй строке, если файл существует (тест [ -f file.txt ] возвращает 0), выполняется удаление. Если файл не существует, команда удаления пропускается.
Такой подход делает скрипты лаконичными и эффективными, однако для сложной логики всё же рекомендуется использовать явные конструкции if-then-else-fi, так как они более читаемы и удобны для отладки.
Тестовые конструкции и операторы сравнения
Для проверки условий в Bash используются специальные тестовые конструкции. Основная из них — это квадратные скобки [ ... ], которые являются псевдонимом для команды test. Также существует расширенная версия [[ ... ]], которая обладает более широкими возможностями и безопаснее при работе с переменными. Внутри этих конструкций можно использовать различные операторы для проверки файлов, строк и чисел.
Проверка файлов является одной из самых частых операций в системном администрировании. Оператор -e проверяет существование любого типа объекта (файла, директории, символической ссылки). Оператор -f проверяет, является ли объект обычным файлом. Оператор -d проверяет, является ли объект директорией. Оператор -x проверяет наличие права на выполнение. Оператор -r проверяет право на чтение, а -w — на запись.
#!/bin/bash
file="/etc/hosts"
if [ -e "$file" ]; then
echo "Файл $file существует"
elif [ -d "$file" ]; then
echo "$file — это директория"
elif [ -f "$file" ]; then
echo "$file — это обычный файл"
else
echo "Объект неизвестного типа"
fi
В этом примере скрипт последовательно проверяет свойства объекта. Сначала он смотрит, существует ли вообще объект по пути. Затем определяет его тип. Важно использовать двойные кавычки вокруг переменной $file, чтобы избежать ошибок, если путь содержит пробелы.
Работа со строками требует отдельного внимания. Оператор = (или ==) проверяет равенство двух строк. Оператор != проверяет неравенство. Оператор -z проверяет, пустая ли строка (длина равна нулю). Оператор -n проверяет, непустая ли строка (длина больше нуля).
#!/bin/bash
user_input=""
if [ -z "$user_input" ]; then
echo "Вы ничего не ввели"
else
echo "Вы ввели: $user_input"
fi
name="Timur"
if [ "$name" = "Timur" ]; then
echo "Имя совпадает"
fi
Числовые сравнения выполняются с помощью специальных операторов. Оператор -eq проверяет равенство. Оператор -ne проверяет неравенство. Оператор -lt проверяет меньше. Оператор -le проверяет меньше или равно. Оператор -gt проверяет больше. Оператор -ge проверяет больше или равно. Важно помнить, что эти операторы работают только с целыми числами. Попытка использовать их с дробными числами приведет к ошибке.
#!/bin/bash
age=25
if [ "$age" -ge 18 ]; then
echo "Пользователь совершеннолетний"
else
echo "Пользователь несовершеннолетний"
fi
count=10
if [ "$count" -lt 20 ]; then
echo "Счетчик меньше двадцати"
fi
Логические операции позволяют объединять несколько условий. Оператор -a (AND) возвращает истину, если оба условия истинны. Оператор -o (OR) возвращает истину, если хотя бы одно из условий истинно. Оператор ! (NOT) инвертирует результат условия. Однако использование квадратных скобок с этими операторами может быть неудобным из-за необходимости экранирования или группировки.
Для более гибкой работы с логикой рекомендуется использовать конструкцию [[ ... ]]. Она поддерживает операторы && (и), || (или) и ! (не) в привычном виде, без необходимости использования специальных символов внутри скобок. Также [[ ... ]] лучше обрабатывает переменные и регулярные выражения.
#!/bin/bash
username="admin"
role="superuser"
if [[ "$username" == "admin" && "$role" == "superuser" ]]; then
echo "Доступ разрешен для администратора"
fi
if [[ ! -f "/etc/shadow" ]]; then
echo "Файл теневых паролей отсутствует"
fi
Внутри [[ ... ]] также доступны операторы сравнения строк == и !=, а также операторы проверки регулярных выражений =~. Регулярные выражения позволяют искать сложные паттерны в тексте, что очень полезно для парсинга логов или валидации входных данных.
#!/bin/bash
email="user@example.com"
if [[ "$email" =~ ^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$ ]]; then
echo "Email валиден"
else
echo "Email невалиден"
fi
Регулярное выражение в примере выше проверяет стандартный формат email. Если строка соответствует шаблону, условие считается истинным.
Существуют также встроенные команды для более сложных проверок, такие как cmp, diff для сравнения файлов, или type для проверки наличия команды в системе. Например, type -t command_name вернет тип команды (alias, function, builtin, keyword, file), что позволяет понять, откуда берется команда при её вызове.
#!/bin/bash
if type -t ls | grep -q "^builtin$"; then
echo "Команда ls является встроенной в оболочку"
else
echo "Команда ls является внешним исполняемым файлом"
fi
Понимание всех доступных тестовых операторов и умение комбинировать их позволяет создавать надежные скрипты, которые корректно обрабатывают любые сценарии использования.
Базовый синтаксис условных операторов if
Конструкция if является основным инструментом ветвления в Bash. Её синтаксис строго регламентирован и требует соблюдения определенных правил оформления. Минимальная структура выглядит следующим образом: ключевое слово if, за которым следует условие (тест), затем ключевое слово then, далее блок кода, который выполняется при истинности условия, и завершается ключевым словом fi (слово if, записанное наоборот).
#!/bin/bash
condition=true
if $condition; then
echo "Условие истинно"
fi
В этом примере переменная condition принимает значение true. При выполнении конструкции if $condition, Bash запускает команду true, которая возвращает 0. Следовательно, блок then выполняется.
Ключевое слово fi обязательно для закрытия блока if. Отсутствие fi приведет к синтаксической ошибке и остановке выполнения скрипта. Также важно соблюдать отступы для улучшения читаемости кода, хотя сам Bash не зависит от них (в отличие от Python).
Стандартная конструкция if-then-else добавляет возможность обработки случая, когда условие ложно. Ключевое слово else размещается между блоком then и блоком fi. Код внутри блока else выполняется только тогда, когда условие в if возвращает ненулевой код.
#!/bin/bash
status=1
if [ "$status" -eq 0 ]; then
echo "Операция прошла успешно"
else
echo "Операция завершилась с ошибкой"
fi
В данном примере статус равен 1, поэтому условие [ "$status" -eq 0 ] ложно. Скрипт перейдет к блоку else и выведет сообщение об ошибке.
Конструкция if-elif-else позволяет проверять несколько условий последовательно. Ключевое слово elif (сокращение от else if) добавляет дополнительные проверки. Условия проверяются сверху вниз. Как только встречается первое истинное условие, выполняется соответствующий ему блок, и остальные условия (elif и else) игнорируются. Если ни одно из условий не истинно, выполняется блок else (если он есть).
#!/bin/bash
score=85
if [ "$score" -ge 90 ]; then
echo "Отлично"
elif [ "$score" -ge 80 ]; then
echo "Хорошо"
elif [ "$score" -ge 70 ]; then
echo "Удовлетворительно"
else
echo "Неудовлетворительно"
fi
В этом примере оценка 85 падает под второе условие (>= 80). Скрипт выведет "Хорошо" и завершит проверку, не обращая внимания на следующие условия.
Важно помнить, что каждый тестовый блок должен быть отделен точкой с запятой или переносом строки. В конструкции [ ... ] перед закрывающей скобкой ] обязательно должна стоять точка с запятой или новыйline, иначе синтаксис будет нарушен.
# Корректно
if [ -f file.txt ]; then
echo "File exists"
fi
# Неверно (пропущена точка с запятой или newline перед fi)
if [ -f file.txt ]
then
echo "File exists"
fi
Второй пример с переносом строки после ] тоже корректен, так как новая строка выполняет роль разделителя. Но первый вариант с точкой с запятой более компактен.
Для удобства чтения и поддержки кода рекомендуется использовать отступы (обычно 4 пробела или 1 табуляцию) внутри блоков if, elif и else. Это помогает быстро визуально определить структуру программы.
if [ condition ]; then
action_1
action_2
elif [ another_condition ]; then
action_3
else
action_4
fi
Также стоит отметить, что в Bash можно вкладывать конструкции if друг в друга (вложенные условия). Это позволяет создавать сложные деревья решений. Однако чрезмерное вложение усложняет чтение кода, поэтому рекомендуется использовать функции или разбивать логику на отдельные блоки.
#!/bin/bash
if [ -d "/var/log" ]; then
if [ -r "/var/log/syslog" ]; then
echo "Можно читать логи"
else
echo "Нет прав на чтение логов"
fi
else
echo "Директория логов не найдена"
fi
В этом примере сначала проверяется существование директории, а затем наличие прав на чтение конкретного файла внутри неё.
Расширенные конструкции case и select
Конструкция case предназначена для выбора действия на основе значения переменной или выражения. Она особенно полезна, когда нужно проверить одно значение на множество возможных вариантов. Синтаксис case более читаем, чем длинная цепочка if-elif-else, когда сравнивается одна и та же переменная с разными константами.
Структура case начинается с ключевого слова case, за которым следует выражение и ключевое слово in. Далее следуют шаблоны (паттерны), за которыми идут действия, завершающиеся двоеточием ;;. Завершает конструкцию ключевое слово esac (слово case, записанное наоборот).
#!/bin/bash
day="monday"
case "$day" in
monday)
echo "Начало недели"
;;
tuesday)
echo "Вторник"
;;
wednesday)
echo "Среда"
;;
*)
echo "Остальные дни"
;;
esac
В этом примере переменная day сравнивается с шаблонами monday, tuesday, wednesday. Если значение совпадает, выполняется соответствующий блок. Шаблон * (звездочка) действует как заглушка (default) и выполняется, если ни одно из предыдущих условий не совпало.
Шаблоны в case поддерживают регулярные выражения и маски. Можно использовать несколько шаблонов через вертикальную черту | для объединения условий.
#!/bin/bash
filename="report.pdf"
case "$filename" in
*.pdf)
echo "Это PDF документ"
;;
*.txt|*.doc|*.docx)
echo "Это текстовый документ"
;;
*.sh)
echo "Это скрипт"
;;
*)
echo "Неизвестный формат"
;;
esac
Здесь файлы с расширениями .txt, .doc, .docx обрабатываются одинаково благодаря использованию оператора |.
Конструкция select создает простое меню для выбора одного из списка элементов. Она автоматически нумерует элементы и запрашивает ввод у пользователя. Результат сохраняется в переменную, выбранную пользователем, и в специальную переменную REPLY.
#!/bin/bash
select option in "Запустить сервер" "Остановить сервер" "Перезагрузить" "Выход"; do
case $option in
"Запустить сервер")
echo "Сервер запущен"
break
;;
"Остановить сервер")
echo "Сервер остановлен"
break
;;
"Перезагрузить")
echo "Сервер перезагружен"
break
;;
"Выход")
echo "Выход из программы"
break
;;
*)
echo "Неверный выбор, попробуйте снова"
;;
esac
done
Конструкция select работает в цикле. Переменная option получает значение выбранного элемента. Если пользователь вводит номер, которого нет в списке, переменная option остается пустой, и срабатывает блок *). Цикл продолжается до тех пор, пока не будет выполнена команда break или не произойдет ошибка ввода.
select часто используется для создания простых интерфейсов в терминальных утилитах, где не требуется сложная логика, но нужен понятный выбор действий для пользователя.
Интерактивное демо — пошаговый цикл на примере JavaScript (
for,while). В Bash синтаксис другой, но порядок шагов тот же. Обобщённо: циклы в коде.
Цикл for: итерация по спискам и диапазонам
Цикл for в Bash предназначен для повторения блока кода для каждого элемента в списке. Это наиболее распространенный цикл для обработки массивов, аргументов командной строки или результатов генерации последовательностей.
Существует два основных варианта записи цикла for. Первый вариант перебирает список значений, указанный явно. Второй вариант использует синтаксис C-like, который удобен для работы с числами.
Вариант со списком:
#!/bin/bash
fruits="apple banana cherry"
for fruit in $fruits; do
echo "Фрукт: $fruit"
done
В этом примере переменная fruits содержит строку с тремя словами. Цикл for разбивает эту строку на слова (по пробелам) и присваивает каждое слово переменной fruit на каждой итерации. Результатом будет вывод трех строк.
Важно использовать кавычки вокруг переменной, если список хранится в переменной, чтобы сохранить пробелы в названиях элементов, если они есть. Однако в простом случае с явным списком слов кавычки не обязательны, так как Bash сам разделяет слова.
#!/bin/bash
colors=(red green blue)
for color in "${colors[@]}"; do
echo "Цвет: $color"
done
Здесь используется массив colors. Обращение "${colors[@]}" гарантирует, что каждый элемент массива будет обработан отдельно, даже если он содержит пробелы.
Вариант с диапазоном чисел (C-style):
#!/bin/bash
for i in {1..5}; do
echo "Номер: $i"
done
Синтаксис {1..5} генерирует последовательность чисел от 1 до 5. Это удобно для создания повторяющихся действий фиксированное количество раз. Диапазон может быть отрицательным или с шагом.
#!/bin/bash
# Обратный порядок
for i in {5..1}; do
echo "Обратный отсчет: $i"
done
# Шаг 2
for i in {0..10..2}; do
echo "Четное число: $i"
done
Второй вариант синтаксиса for (как в C):
#!/bin/bash
for (( i=0; i<5; i++ )); do
echo "Итерация: $i"
done
Этот синтаксис включает три части: инициализацию (i=0), условие продолжения (i<5) и шаг изменения (i++). Цикл выполняется, пока условие истинно. Это дает полный контроль над счетчиком и условиями остановки.
#!/bin/bash
# Работа с аргументами скрипта
for arg in "$@"; do
echo "Аргумент: $arg"
done
Переменная $@ содержит все аргументы, переданные скрипту при запуске. Цикл for позволяет обработать каждый аргумент индивидуально. Это часто используется в утилитах, которые принимают список файлов для обработки.
#!/bin/bash
# Обработка файлов в директории
for file in *.txt; do
if [ -f "$file" ]; then
echo "Обработка файла: $file"
# Здесь можно добавить логику обработки
fi
done
Глобальный шаблон *.txt расширяется оболочкой в список всех файлов с расширением .txt в текущей директории. Цикл проходит по каждому найденному файлу. Проверка [ -f "$file" ] необходима, так как если файлов с таким расширением нет, шаблон останется неизменным (строка *.txt), и проверка предотвратит ложное действие.
Цикл for также поддерживает конструкцию continue и break для управления потоком выполнения. continue пропускает оставшуюся часть текущей итерации и переходит к следующей. break полностью прекращает выполнение цикла.
#!/bin/bash
for i in 1 2 3 4 5; do
if [ "$i" -eq 3 ]; then
continue
fi
echo "Число: $i"
done
В этом примере число 3 пропускается, и вывод будет: 1, 2, 4, 5.
Цикл while: повторение по условию
Цикл while в Bash выполняет блок кода многократно, пока условие остаётся истинным. В отличие от цикла for, который обычно имеет известное количество итераций или список элементов, цикл while подходит для ситуаций, когда количество повторений заранее неизвестно и зависит от динамических условий.
Синтаксис цикла while:
while [ условие ]; do
тело_цикла
done
Условие проверяется перед каждой итерацией. Если условие истинно (возвращает 0), выполняется тело цикла. После выполнения тела цикла условие проверяется снова. Процесс повторяется до тех пор, пока условие не станет ложным.
Пример бесконечного цикла с явным прерыванием:
#!/bin/bash
counter=0
while true; do
echo "Итерация: $counter"
counter=$((counter + 1))
if [ "$counter" -ge 5 ]; then
break
fi
done
Ключевое слово true всегда возвращает 0, поэтому условие [ true ] всегда истинно. Цикл будет выполняться бесконечно, пока не встретится команда break, которая принудительно останавливает выполнение. Это полезный паттерн, когда условие выхода зависит от внутреннего состояния программы, а не от внешнего фактора.
Пример проверки наличия процесса:
#!/bin/bash
process_name="apache2"
while ! pgrep -x "$process_name" > /dev/null; do
echo "Процесс $process_name не запущен. Жду..."
sleep 5
done
echo "Процесс $process_name найден!"
В этом примере используется команда pgrep для поиска процесса. Оператор ! инвертирует результат: если процесс не найден (команда возвращает 1), условие становится истинным, и цикл продолжает ждать. Как только процесс находится (команда возвращает 0), условие становится ложным, и цикл завершается.
Пример чтения файла построчно:
#!/bin/bash
file="Данные.txt"
while IFS= read -r line; do
echo "Строка: $line"
done < "$file"
Команда read считывает строку из файла, указанного в перенаправлении < "$file". Флаг -r предотвращает интерпретацию обратных слэшей как escape-символов. Переменная IFS (Internal Field Separator) устанавливается в пустое значение, чтобы сохранить ведущие и trailing пробелы в строке. Цикл продолжается, пока read успешно читает строки. Когда файл заканчивается, read возвращает ненулевой код, и цикл завершается.
Пример взаимодействия с пользователем:
#!/bin/bash
while true; do
echo "Выберите действие:"
echo "1. Добавить"
echo "2. Удалить"
echo "3. Выйти"
read -p "Введите номер: " choice
case $choice in
1)
echo "Добавление..."
;;
2)
echo "Удаление..."
;;
3)
echo "Выход"
break
;;
*)
echo "Неверный выбор"
;;
esac
done
Здесь цикл ожидает ввода пользователя. Пока пользователь не выберет вариант 3, цикл будет продолжать показывать меню и обрабатывать ввод.
Важно помнить, что тело цикла должно изменять состояние, которое влияет на условие проверки, иначе цикл может стать бесконечным. Например, если в примере с pgrep забыть увеличить таймер или изменить переменную, скрипт зависнет.
Также стоит отметить, что в while можно использовать любую команду, возвращающую код выхода, а не только тесты в скобках. Это делает цикл очень гибким.
#!/bin/bash
# Цикл, пока команда wget не скачает файл успешно
while ! wget -q http://example.com/file.zip; do
echo "Ошибка загрузки. Повторяю через 10 секунд..."
sleep 10
done
Если wget успешно скачивает файл (код 0), условие ! wget становится ложным, и цикл завершается. Если происходит ошибка, wget возвращает ненулевой код, ! инвертирует его в истину, и цикл повторяется.
Цикл until: повторение до достижения условия
Цикл until является обратной стороной цикла while. Он выполняет блок кода до тех пор, пока условие ложно. Как только условие становится истинным, цикл завершается. Это удобно, когда нужно ждать наступления определенного события, и логика проще формулируется как "повторять, пока событие НЕ произошло".
Синтаксис цикла until:
until [ условие ]; do
тело_цикла
done
Разница с while только в логике проверки. while работает, пока условие истинно. until работает, пока условие ложно.
Пример ожидания появления файла:
#!/bin/bash
target_file="/tmp/data_ready.txt"
until [ -f "$target_file" ]; do
echo "Файл $target_file ещё не создан. Жду..."
sleep 2
done
echo "Файл появился!"
В этом скрипте цикл будет работать, пока файл не существует. Как только файл появится (условие [ -f "$target_file" ] вернет 0, то есть истину), цикл until завершится. Это более естественная формулировка задачи: "Жди, пока файл не появится", чем "Пока файл не появится, жди" (что потребовало бы использования ! в while).
Пример ожидания завершения процесса:
#!/bin/bash
process_id=12345
until ! kill -0 $process_id 2>/dev/null; do
echo "Процесс $process_id всё ещё работает..."
sleep 1
done
echo "Процесс $process_id завершился"
Команда kill -0 проверяет существование процесса, не отправляя сигнал. Если процесс существует, она возвращает 0. Если процесс не существует, возвращает 1. Условие ! kill -0 будет истинным, пока процесс НЕ существует. То есть цикл until будет выполняться, пока процесс существует (условие ложно). Как только процесс завершится, kill -0 вернет 1, ! превратит это в 0 (истину), и цикл выйдет.
Важно понимать, что until и while могут выполнять одни и те же задачи, просто с разной логикой формулировки условий. Выбор между ними зависит от того, какая формулировка кажется более интуитивной для конкретной задачи.
#!/bin/bash
# Вариант 1: while с инверсией
while [ ! -f "/tmp/done.txt" ]; do
echo "Жду..."
sleep 1
done
# Вариант 2: until (эквивалентно)
until [ -f "/tmp/done.txt" ]; do
echo "Жду..."
sleep 1
done
Оба варианта делают одно и то же. Второй вариант короче и читаемее, так как не требует оператора !.
Цикл until также поддерживает команды break и continue для управления потоком, точно так же, как и другие циклы.
#!/bin/bash
attempt=0
until [ "$attempt" -ge 3 ]; do
attempt=$((attempt + 1))
echo "Попытка $attempt"
# Имитация случайного успеха
if [ $((RANDOM % 3)) -eq 0 ]; then
echo "Успех достигнут!"
break
fi
done
В этом примере цикл пытается достичь успеха максимум 3 раза. Если успех наступает раньше, цикл прерывается командой break.
Управление потоком: break, continue и exit
Внутри циклов и условных конструкций в Bash доступны специальные команды для управления потоком выполнения: break, continue и exit. Эти команды позволяют гибко управлять логикой скрипта, пропускать итерации или завершать выполнение целиком.
Команда break немедленно завершает выполнение текущего цикла. Если циклы вложены, break завершает только самый внутренний цикл. Чтобы выйти из нескольких уровней вложенности сразу, можно указать количество уровней.
#!/bin/bash
for i in 1 2 3 4 5; do
for j in a b c; do
if [ "$j" = "b" ]; then
break
fi
echo "i=$i, j=$j"
done
done
В этом примере, когда j становится равным b, внутренний цикл прерывается. Внешний цикл продолжается. Вывод будет:
i=1, j=a
i=2, j=a
i=3, j=a
i=4, j=a
i=5, j=a
Если нужно прервать внешний цикл из внутреннего, используется break 2.
#!/bin/bash
for i in 1 2 3 4 5; do
for j in a b c; do
if [ "$j" = "b" ]; then
break 2
fi
echo "i=$i, j=$j"
done
done
Теперь при достижении b прервется и внешний цикл. Вывод будет только для i=1, j=a.
Команда continue пропускает оставшуюся часть текущей итерации цикла и переходит к следующей. Она полезна для фильтрации элементов, которые не подходят под определенные условия.
#!/bin/bash
for i in 1 2 3 4 5 6 7 8 9 10; do
if [ $((i % 2)) -eq 0 ]; then
continue
fi
echo "Нечетное число: $i"
done
В этом примере четные числа пропускаются, и выводятся только нечетные.
Команда exit завершает выполнение всего скрипта с указанным кодом выхода. Если код не указан, используется код выхода последней выполненной команды.
#!/bin/bash
if [ "$1" = "" ]; then
echo "Ошибка: необходим аргумент"
exit 1
fi
echo "Аргумент получен: $1"
Использование exit с ненулевым кодом (например, 1) сообщает операционной системе или родительскому процессу, что скрипт завершил работу с ошибкой. Это стандартный способ сигнализировать об ошибках в bash-скриптах.
Также существует команда return, которая работает аналогично exit, но только внутри функций. Она завершает выполнение функции и возвращает управление вызвавшему коду.
my_function() {
if [ "$1" -lt 0 ]; then
return 1
fi
echo "Число положительное"
return 0
}
if my_function -5; then
echo "Функция вернула успех"
else
echo "Функция вернула ошибку"
fi
В этом примере функция возвращает 1, если число отрицательное. Скрипт проверяет результат вызова функции и выводит соответствующее сообщение.
Правильное использование этих команд делает скрипты более надежными и предсказуемыми. break и continue помогают оптимизировать логику циклов, избегая лишних проверок. exit и return обеспечивают корректную обработку ошибок и завершение работы.
Примеры
Написание качественных bash-скриптов требует не только знания синтаксиса, но и понимания лучших практик. Ниже приведены примеры комплексных сценариев, демонстрирующих сочетание условных операторов и циклов.
Пример: Скрипт резервного копирования с проверкой места на диске.
#!/bin/bash
BACKUP_DIR="/backup"
SOURCE_DIR="/home/user/Данные"
MIN_SPACE_MB=100
# Проверка существования директории резервных копий
if [ ! -d "$BACKUP_DIR" ]; then
echo "Создание директории резервных копий..."
mkdir -p "$BACKUP_DIR"
fi
# Проверка свободного места
free_space=$(df -m "$BACKUP_DIR" | awk 'NR==2 {print $4}')
if [ "$free_space" -lt "$MIN_SPACE_MB" ]; then
echo "Ошибка: недостаточно места на диске. Доступно: ${free_space}MB, требуется: ${MIN_SPACE_MB}MB"
exit 1
fi
echo "Начало резервного копирования..."
# Копирование файлов
for file in "$SOURCE_DIR"/*; do
if [ -f "$file" ]; then
filename=$(basename "$file")
echo "Копирование: $filename"
# Проверка успешности копирования
if cp "$file" "$BACKUP_DIR/"; then
echo "Успешно: $filename"
else
echo "Ошибка копирования: $filename"
# Продолжаем копирование остальных файлов
continue
fi
fi
done
echo "Резервное копирование завершено."
В этом скрипте используется if для проверки условий (существование директории, свободное место). Цикл for перебирает файлы. Внутри цикла используется проверка результата команды cp. Если копирование неудачно, используется continue для пропуска текущего файла и перехода к следующему.
Пример: Мониторинг сервиса с перезапуском при сбоях.
#!/bin/bash
SERVICE_NAME="nginx"
MAX_RESTARTS=3
restart_count=0
while true; do
if pgrep -x "$SERVICE_NAME" > /dev/null; then
echo "$(date): Сервис $SERVICE_NAME работает"
restart_count=0
else
echo "$(date): Сервис $SERVICE_NAME не работает. Перезапуск..."
systemctl start "$SERVICE_NAME"
restart_count=$((restart_count + 1))
if [ "$restart_count" -ge "$MAX_RESTARTS" ]; then
echo "$(date): Достигнуто максимальное количество попыток перезапуска ($MAX_RESTARTS)"
exit 1
fi
fi
sleep 60
done
Этот скрипт работает в бесконечном цикле while true. Каждую минуту он проверяет статус сервиса. Если сервис не работает, он пытается его перезапустить. Счетчик перезапусков увеличивается. Если количество попыток превышает лимит, скрипт завершается с ошибкой. Это защищает систему от бесконечного цикла перезагрузки сломанного сервиса.
Пример: Обработка аргументов командной строки с валидацией.
#!/bin/bash
usage() {
echo "Использование: $0 <действие> [параметры]"
echo "Действия: start, stop, status"
exit 1
}
if [ $# -lt 1 ]; then
usage
fi
action="$1"
case "$action" in
start)
echo "Запуск сервиса..."
;;
stop)
echo "Остановка сервиса..."
;;
status)
echo "Проверка статуса..."
;;
*)
echo "Неизвестное действие: $action"
usage
;;
esac
Этот скрипт демонстрирует использование case для обработки команд и функции usage для вывода справки. Аргументы проверяются на наличие, и если их нет, вызывается usage с exit 1.
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). История развития оболочек представляет собой непрерывный процесс эволюции технологий. Каждая новая версия решала конкретные проблемы своих предшественников и добавляла новые возможности. Стандартные утилиты командной строки включают — Файловые операции — ls, cd, mkdir, rm, cp, mv, chmod, chown, Текстовая обработка — cat, grep, sed, awk, sort, uniq, cut, head, tail, Системная… Примеры переменных окружения — HOME — домашняя директория пользователя, PATH — список директорий для поиска исполняемых файлов, USER — имя текущего пользователя, PWD — текущая рабочая директория Кавычки, точки, запятые, скобки и прочие знаки препинания. Язык Bash предоставляет богатый набор средств для управления логикой выполнения скрипта. Эти конструкции позволяют принимать решения на основе условий и повторять действия многократно. Оболочка… Встроенная команда — это функция, реализованная непосредственно внутри процесса интерпретатора командной строки. Оболочка выполняет такие команды без создания отдельного дочернего процесса. Код… Подстановка значений — это механизм, при котором оболочка заменяет имя переменной на её фактическое значение перед выполнением команды. Существует несколько способов расширения переменных, каждый из… Локальная переменная в Bash — это переменная, чья область видимости ограничена телом функции. Такая переменная не влияет на глобальные переменные с тем же именем, существующие вне функции.… Файловая система — это метод организации хранения данных на носителе информации, обеспечивающий упорядоченное размещение файлов и каталогов. Обработка ошибок в Bash базируется на нескольких основных подходах. Выбор метода зависит от контекста задачи и требований к надежности скрипта. Программа htop представляет собой улучшенную версию стандартной утилиты top. Она предлагает графический интерфейс с возможностью прокрутки списка процессов, цветового кодирования и интерактивного… Гайд по установке и настройке с написанием первой программы и её запуском.История оболочки Bash
Экосистема скриптов и автоматизации на Bash
Основы языка Bash
Синтаксис и специальные символы в Bash
Ключевые слова и зарезервированные конструкции
Встроенные команды и функции оболочки
Переменные и подстановка значений
Функции и локальные переменные
Работа с файлами, каталогами и процессами
Обработка ошибок и коды возврата
Популярные утилиты и примеры скриптов
Первая программа на Bash