Вторая часть статьи продолжает тему условных конструкций в bash, начатую в предыдущей части.
При составлении скриптов часто требуется выполнить разные фрагменты кода в зависимости от истинности или ложности того или иного условия. В языке bash для реализации таких сценариев предусмотрено несколько условных операторов. Сегодня поговорим об условных операторах if
и case
, а также логических операторах &&
и ||
.
Оператор if
Один из основных условных операторов в языке bash, как и в большинстве языков программирования, — оператор if
(иногда его называют if-then-else
).
Общий вид конструкции:
С одним условием и одним предусмотренным вариантом действий (либо их отсутствием):
if условие a; then
блок кода 1
fi
С одним условием и двумя вариантами действий:
if условие a; then
блок кода 1
else
блок кода 2
fi
С двумя условиями и тремя вариантами действий:
if условие a; then
блок кода 1
elif условие b; then
блок кода 2
else
блок кода 3
fi
После оператора if
помещают тело проверяемого условия. Таким условием могут быть следующие операторы и команды:
1. Одинарные квадратные скобки [ ... ] | Одинарные скобки — способ записи команды test — позволяют сравнивать строки. |
|
2. Команда test |
| |
3. Двойные квадратные скобки [[ ... ]] | Позволяют проверять условия, поддерживают сравнение с шаблонами (== с подстановочными знаками) и регулярными выражениями (=~), а также (без экранирования) логические операторы && и || |
|
4. Арифметические вычисления(( ... )) | Используются для сравнения чисел (в частности, чисел с плавающей запятой) и обрабатывают выражение внутри двойных скобок как математическое выражение |
|
5. Сравнение строк в двойных скобках [[ ... ]] с регулярными выражениями | При помощи оператора =~ внутри двойных скобок [[ ... ]] строки сравнивают с регулярными выражениями. |
|
6. Подстановка команд | В качестве условия можно использовать любую команду или скрипт: они возвращают true, если код возврата равен 0, и false, если ненулевой. |
|
7. Отрицание ! | Символ ! используют для отрицания проверяемого условия: оно возвращает true, если код возврата ненулевой, и false, если он равен 0. |
|
Код возврата (exit status, exit code) завершённого дочернего процесса — целое число, которое оболочка сообщает родительскому процессу. Код 0 означает успешное завершение команды, другие коды (как правило, 1) — ошибку.
Скобки (одинарные или двойные — об этой конструкции рассказано в этом разделе) должны отделяться от тела условия пробелами.
Конструкция if-then-else
означает, что если условие а истинно, следует выполнить блок кода 1.
Ниже приведены примеры скриптов с одним, двумя и тремя предусмотренными вариантами действий в зависимости от результата проверки условия.
if — then
: в зависимости от результата проверки выполняется действие, либо скрипт завершается.Этот скрипт проверяет, запущена ли служба
sshd
.if systemctl is-active --quiet sshd; then echo "Служба sshd запущена." fi
Если служба запущена, подстановка команды systemctl is-active --quiet sshd
возвращает 0. В этом случае выполняется блок then
: отображается сообщение "Служба sshd запущена.
"
Если служба не запущена, условие не выполняется, команда echo
внутри then
пропускается, и работа оператора if
завершается.
if — then — else: в зависимости от результата проверки производится либо одно действие, либо другое.
if [ -f "/etc/passwd" ]; then echo "Файл /etc/passwd существует." else echo "Файл /etc/passwd не существует." fi
Условие [ -f "/etc/passwd"
] проверяет, существует ли /etc/passwd
как обычный файл.
Если файл существует, код возврата команды test
— 0 (успешное завершение). Выполняется блок then
: отображается сообщение "Файл /etc/passwd существует."
Если файл не существует, команда test
возвращает код 1 (ошибка). Выполняется блок else
: отображается сообщение "Файл /etc/passwd не существует."
if — elif — else
: в зависимости от результата проверки производится либо одно, либо второе, либо третье действие.Этот скрипт проверяет, является ли число в переменной num положительным, отрицательным или равно 0:
num=-5 if (( num > 0 )); then echo "Число положительное." elif (( num < 0 )); then echo "Число отрицательное." else echo "Число — 0." fi
После оператора else
указывают финальный вариант хода скрипта — действия по умолчанию: они выполняются, если все вышеперечисленные условия оказались ложными.
Конец действия оператора if
обозначается ключевым словом fi
. Если не поставить его в конце, выполнение скрипта прервётся с сообщением об ошибке.
Отступы от начала строки внутри блоков кода с операторами if
и else
не обязательны для интерпретации кода оболочкой, но упрощают его чтение для человека.
Если имеется более двух вариантов хода скрипта в зависимости от значения единственной переменной, удобнее использовать оператор case
— работа с ним описана в разделе Оператор case.
Оператор case
В bash-скриптах оператор case
используется для выполнения различных блоков команд в зависимости от истинности того или иного условия. Этот способ позволяет обработать в условной конструкции несколько вариантов значения одной переменной более компактно, чем с оператором if
.
Каждый вариант — строка после case … in
— описывает одно условие и выполняет набор команд, если условие истинно (то есть код возврата — 0).
- Если действие должно выполняться при истинности одного условия, строку
case … in
завершают ограничителем — двумя точками с запятой без пробела между ними;;
- Если действие должно выполняться в случае истинности нескольких условий, эти условия разделяют символами
;;&
После обнаружения истинности одного условия (или нескольких, объединённых символом ;;&
) программа завершает работу оператора case, не переходя к чтению прочих условий.
Хорошая практика — указывать подстановочный знак * (звёздочка, asterisk) в качестве финального условия, определяющего действие по умолчанию: например, вывод сообщения. Финальное условие срабатывает во всех случаях, не описанных в предыдущих условиях, и если они оказались ложными, выполняется действие по умолчанию.
Синтаксис условия в общем виде:
case $variable in
вариант1)
действие1
;;
вариант2)
действие2
;;
*)
действие3
;;
esac
Следующий скрипт принимает на вход вводимое с клавиатуры имя пользователя и совершает с его учётной записью одно из указанных действий: добавление, удаление, блокировку или снятие блокировки.
#!/bin/bash
read -p "Укажите имя пользователя: " username
read -p "Укажите действие {add|remove|lock|unlock}: " action
case $action in
add)
sudo useradd -m "$username" && echo "Пользователь $username добавлен."
;;
remove)
sudo userdel -r "$username" && echo "Пользователь $username удалён."
;;
lock)
sudo usermod -L "$username" && echo "Пользователь $username заблокирован."
;;
unlock)
sudo usermod -U "$username" && echo "Пользователь $username разблокирован."
;;
*)
echo "Выберите одно из этих действий: {add|remove|lock|unlock}"
;;
esac
- После запуска скрипта в консоли отображается сообщение: Укажите имя пользователя:
- Пользователь вводит имя пользователя.
- Оператор
read
считывает введённое пользователем значение и помещает его в переменнуюusername
. - Отображается сообщение:
Укажите действие {add|remove|lock|unlock}:
- Пользователь указывает одно из указанных действий.
- Оператор
read
считывает введённое пользователем значение и помещает его в переменнуюaction
. - Если указано действие
add
, командаsudo useradd -m "$username"
добавляет пользователя с указанным именем.- Если пользователь успешно создан, отображается сообщение Пользователь <имя пользователя> добавлен.
- Если такой пользователь уже существует, отображается сообщение об ошибке от утилиты useradd: user <имя пользователя> already exists.
- Если указаны действия
remove
(удаление),lock
(блокировка) илиunlock
(снятие блокировки), действия выполняются по аналогичному алгоритму, только с командамиuserdel
иusermod
, соответственно: если команда выполнена успешно, отображается сообщение об этом, если нет — сообщение об ошибке от соответствующей утилиты. - Если введён набор символов, не входящий в указанные варианты, отображается сообщение: Выберите одно из этих действий: {add|remove|lock|unlock}
If или case?
Для некоторых скриптов можно выбрать один из двух условных операторов:
- конструкцию
if
с одним или несколькими операторамиelif
либо вложеннымиif
-конструкциями - или оператор
case
для описания тех же вариантов действий.
Если нужно сравнить значение одной переменной с несколькими эталонными значениями, рекомендуется выбирать оператор case
: в этом случае код выглядит лаконичнее, его проще и быстрее читать.
Для работы с несколькими переменными удобнее использовать оператор if
. Во всех случаях рекомендуется учитывать, насколько человеку будет удобно читать и поддерживать скрипт.
Логические операторы && и ||
Операторы &&
(логическое И, конъюнкция) и ||
(логическое ИЛИ, дизъюнкция) часто встречаются в скриптах и в цепочках команд, в том числе в конструкциях с оператором if
. Они позволяют обусловить выполнение следующей команды результатом выполнения предыдущей.
Конструкция && (логическое И)
Синтаксис в общем виде:
команда1 && команда2
Если первая команда выполняется с кодом возврата 0, то есть успешно — выполняется вторая команда. Если выполнение команды1
завершается с кодом 1 (с ошибкой), то оболочка не переходит к команде2
и отображается сообщение об ошибке: например, о том, что файла или каталога не существует.
Пример:
sudo apt update && sudo apt install ffmpeg
sudo
позволяет непривилегированному пользователю выполнить действия, требующие прав суперпользователя;apt update
обновляет список доступных пакетов программного обеспечения из официальных репозиториев;apt install ffmpeg
устанавливает (или обновляет) указанный пакет только в том случае, если удалось обновить список пакетов.
Общий вид синтаксиса при использовании оператора &&
в условных конструкциях с if
:
С одной операцией конъюнкции условий и одним предусмотренным вариантом действий:
if условие а && условие b; then
блок кода 1
fi
С одной операцией конъюнкции условий и двумя предусмотренными вариантами действий:
if условие а && условие b; then
блок кода 1
else
блок кода 2
fi
С операцией конъюнкции условий, дополнительным условием и тремя предусмотренными вариантами действий:
if условие а && условие b; then
блок кода 1
elif условие x; then
блок кода 2
else
блок кода 3
fi
Следующий скрипт запускает веб-сервер nginx, если он не запущен:
#!/bin/bash
process="nginx"
if ! pgrep -x "$process" >/dev/null && sudo systemctl start "$process"; then
echo "Теперь $process запущен."
else
echo "$process уже работает, либо его не удалось запустить."
fi
- Переменной
process
присваивается имя нужного процесса — в данном случае веб-сервераnginx
. - Если
pgrep
не находит (отрицание обозначается символом!
) среди запущенных процессовnginx
(по ключу-х
ищется точное соответствие указанной строке; выводpgrep
не отображается на экране, а подавляется путем перенаправления на нулевое устройство/dev/null
), оболочка переходит к команде после оператора&&
— от имени суперпользователя запускается процессnginx
.
Нулевое устройство — это файл устройства, который уничтожает все записанные в него данные, но сообщает, что операция записи прошла успешно. В UNIX-системах оно обозначается как /dev/null.
- Отображается сообщение: Теперь
nginx
запущен. - Если команда между операторами
if
и&&
выполнена, то есть процессnginx
найден в списке запущенных процессов, отображается сообщение:nginx уже работает, либо его не удалось запустить.
Конструкция || (логическое ИЛИ)
Синтаксис в общем виде:
команда1 || команда2
В этой комбинации команда2
выполняется только в случае, если выполнение команды1
завершилось кодом 1 (ошибка). Если команда1
вернула код 0 (успешное завершение), то комбинация прервётся — будет выполнена только команда1
.
Пример:
dpkg -l | grep -qw "ffmpeg" || sudo apt install ffmpeg
Вывод утилиты dpkg -l
(вывести список установленных программ) передаётся утилите grep
.
grep
с ключами-q
(вывод grep не отображается на экране; поиск завершается после первого найденного соответствия) и-w
(поиск точного соответствия указанной строке) ищет среди установленных имя пакетаffmpeg
.- Если пакет установлен, вторая команда
sudo apt install ffmpeg
не запускается. Если он отсутствует в системе, запускается установкаffmpeg
.
Общий вид синтаксиса при использовании оператора ||
в условных конструкциях с if
:
if условие а || условие b; then
блок кода 1
elif условие x; then # Если требуется указать дополнительный
# вариант результата сравнения
блок кода 2
else
блок кода 3
fi
Следующий скрипт также запускает веб-сервер nginx, если он не запущен, но проверяет наличие запущенного процесса по другой схеме:
#!/bin/bash
process="nginx"
if pgrep -x "$process" >/dev/null || sudo systemctl start "$process"; then
echo "$process уже работает или успешно запущен"
else
echo "Не удалось запустить $process"
fi
- Переменной
process
присваивается имя нужного процесса — в данном случае веб-сервераnginx
. - Только если команда между операторами
if
и||
(pgrep
находит среди запущенных процессов строку сnginx
; вывод этой команды не отображается на экране, а подавляется путем перенаправления в/dev/null
) не выполняется, оболочка переходит к команде после||
— от имени суперпользователя запускается процессnginx
. - Отображается сообщение:
nginx
уже работает или успешно запущен. - Если команда между операторами
if
и||
не выполнена, то есть процессnginx
найден в списке запущенных процессов, отображается сообщение:Не удалось запустить nginx.
Если условие заключается в одинарные квадратные скобки [ … ]
, вместо &&
используют оператор -a (AND)
, вместо ||
указывают оператор -o (OR)
.
Корректная запись операторов &&
и ||
в одинарных квадратных скобках:
[ a = a -a b = b ]
[ a = a -o b = b ]
Корректная запись операторов &&
и ||
в двойных квадратных скобках:
[[ a = a && b = b ]]
[[ a = a || b = b ]]
Законы де Моргана
Законы де Моргана позволяют упростить логические выражения, в которых есть операторы II
и &&
с отрицаниями — например, при проверке строк или чисел на соответствие определённым условиям.
Законы формулируются так:
НЕ (A И B) = (НЕ A) ИЛИ (НЕ B)
НЕ (A ИЛИ B) = (НЕ A) И (НЕ B)
Вот как можно применить законы де Моргана при сравнении строк и чисел в bash.
Сравнение строк
Следующее условие проверяет, что значение переменной name
— не angle
и не line
:
Исходное выражение: ! ( "$name" == "angle" || "$name" == "line" )
Результат преобразования де Моргана: "$name" != "angle" && "$name" != "line"
Сравнение чисел
Следующее условие проверяет, что число х
не входит в диапазон от 10 до 20 включительно:
Исходное выражение: ! ( x >= 10 && x <= 20 )
Результат преобразования де Моргана: x < 10 || x > 20
Подведем итоги: из двух частей этой статьи мы узнали, что условные операторы if
и case
позволяют выбирать между вариантами дальнейшего хода скрипта, а логические конструкции &&
(логическое И) и ||
(логическое ИЛИ) позволяют связать выполнение следующей команды с результатом выполнения предыдущей.
Без условных конструкций не обойтись как при написании скриптов для базовой автоматизации, так и при создании сложных сценариев с вложенными условиями.