Тариф успешно добавлен в корзину
В корзину
url image

Условные операторы в bash. Часть 2

Вторая часть статьи продолжает тему условных конструкций в 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 — позволяют сравнивать строки.

if [ "$var" = "значение" ]; then

  echo "Значение переменной ${var} равно строке ‘значение’"

fi

2. Команда test

if test "$var" = "value"; then

  echo "Значение переменной ${var} равно строке ‘значение’"

fi

3. Двойные квадратные скобки [[ ... ]]Позволяют проверять условия, поддерживают сравнение с шаблонами (== с подстановочными знаками) и  регулярными выражениями (=~), а также  (без экранирования) логические операторы && и ||

if [[ "$var" == "value" && "$number" -gt 5 ]]; then

  echo "Значение переменной ${var} равно строке ‘значение’. Значение переменной ${number} больше 5"

fi

4. Арифметические вычисления(( ... ))Используются для сравнения чисел (в частности, чисел с плавающей запятой) и обрабатывают выражение внутри двойных скобок как математическое выражение

if (( number > 5 )); then

  echo "Число больше 5"

fi

5. Сравнение строк в двойных скобках [[ ... ]] с регулярными выражениямиПри помощи оператора =~ внутри двойных скобок [[ ... ]] строки сравнивают с регулярными выражениями.

if [[ "$string" =~ ^[A-Za-z]+$ ]]; then

  echo "Строка содержит только буквы"

fi

6. Подстановка командВ качестве условия можно использовать любую команду или скрипт: они возвращают true, если код возврата равен 0, и false, если ненулевой.

if grep -q "набор_символов" file.txt; then

  echo "Последовательность найдена в файле"

fi

7. Отрицание !Символ ! используют для отрицания проверяемого условия: оно возвращает true, если код возврата ненулевой, и false, если он равен 0.

if ! grep -q "набор_символов" file.txt; then

  echo "Последовательность не найдена в файле"

fi

Код возврата (exit status, exit code) завершённого дочернего процесса — целое число, которое оболочка сообщает родительскому процессу. Код 0 означает успешное завершение команды, другие коды (как правило, 1) — ошибку.

Скобки (одинарные или двойные — об этой конструкции рассказано в этом разделе) должны отделяться от тела условия пробелами.

Конструкция if-then-else означает, что если условие а истинно, следует выполнить блок кода 1

Ниже приведены примеры скриптов с одним, двумя и тремя предусмотренными вариантами действий в зависимости от результата проверки условия.

  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 завершается.

  1. 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 не существует."

  1. 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
  1. После запуска скрипта в консоли отображается сообщение: Укажите имя пользователя: 
  2. Пользователь вводит имя пользователя.
  3. Оператор read считывает введённое пользователем значение и помещает его в переменную username.
  4. Отображается сообщение: Укажите действие {add|remove|lock|unlock}:  
  5. Пользователь указывает одно из указанных действий.
  6. Оператор read считывает введённое пользователем значение и помещает его в переменную action.
  7. Если указано действие add, команда sudo useradd -m "$username" добавляет пользователя с указанным именем. 
    1. Если пользователь успешно создан, отображается сообщение Пользователь <имя пользователя> добавлен
    2. Если такой пользователь уже существует, отображается сообщение об ошибке от утилиты useradd: user <имя пользователя> already exists.
  8. Если указаны действия remove (удаление), lock (блокировка) или unlock (снятие блокировки), действия выполняются по аналогичному алгоритму, только с командами userdel и usermod, соответственно: если команда выполнена успешно, отображается сообщение об этом, если нет — сообщение об ошибке от соответствующей утилиты.
  9. Если введён набор символов, не входящий в указанные варианты, отображается сообщение: Выберите одно из этих действий: {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
  1. sudo позволяет непривилегированному пользователю выполнить действия, требующие прав суперпользователя;
  2. apt update обновляет список доступных пакетов программного обеспечения из официальных репозиториев;
  3. 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
  1. Переменной process присваивается имя нужного процесса — в данном случае веб-сервера nginx.
  2. Если pgrep не находит (отрицание обозначается символом !) среди запущенных процессов nginx (по ключу  ищется точное соответствие указанной строке; вывод pgrep не отображается на экране, а подавляется путем перенаправления на нулевое устройство /dev/null), оболочка переходит к команде после оператора && — от имени суперпользователя запускается процесс nginx.

Нулевое устройство — это файл устройства, который уничтожает все записанные в него данные, но сообщает, что операция записи прошла успешно. В UNIX-системах оно обозначается как /dev/null.

  1. Отображается сообщение: Теперь nginx запущен.
  2. Если команда между операторами if и && выполнена, то есть процесс nginx найден в списке запущенных процессов, отображается сообщение: nginx уже работает, либо его не удалось запустить.

Конструкция || (логическое ИЛИ)

Синтаксис в общем виде:

команда1 || команда2

В этой комбинации команда2 выполняется только в случае, если выполнение команды1 завершилось кодом 1 (ошибка). Если команда1 вернула код 0 (успешное завершение), то комбинация прервётся — будет выполнена только команда1

Пример:

dpkg -l | grep -qw "ffmpeg" || sudo apt install ffmpeg

Вывод утилиты dpkg -l (вывести список установленных программ) передаётся утилите grep.

  1. grep с ключами -q (вывод grep не отображается на экране; поиск завершается после первого найденного соответствия) и  -w (поиск точного соответствия указанной строке) ищет среди установленных имя пакета ffmpeg.
  2. Если пакет установлен, вторая команда 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
  1. Переменной process присваивается имя нужного процесса — в данном случае веб-сервера nginx.
  2. Только если команда между операторами if и || (pgrep находит среди запущенных процессов строку с nginx; вывод этой команды не отображается на экране, а подавляется путем перенаправления в /dev/nullне выполняется, оболочка переходит к команде после || — от имени суперпользователя запускается процесс nginx.
  3. Отображается сообщение: nginx уже работает или успешно запущен.
  4. Если команда между операторами 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 позволяют выбирать между вариантами дальнейшего хода скрипта, а логические конструкции && (логическое И) и || (логическое ИЛИ) позволяют связать выполнение следующей команды с результатом выполнения предыдущей.

Без условных конструкций не обойтись как при написании скриптов для базовой автоматизации, так и при создании сложных сценариев с вложенными условиями.

Этот материал был полезен?

Скидка новым клиентам
Закажите сервер сегодня и получите скидку на первый месяц аренды!