Перевод статьи «Bash If Statements: Beginner to Advanced».

Bash это интересный язык. Изначально он был создан для интерактивного использования в командной строке, главным образом в REPL (Read-Evaluate-Print-Loop – «цикл чтение-вычисление-вывод») для работы над одной командой пользователя за раз.
Этот язык спроектирован для существенной, но при этом лаконичной обработки простого текста. У этого есть побочный эффект: немного сложнее выполнять вещи, которые не являются интерактивными или связанными с текстом, например, арифметические вычисления, контроль потоков и манипуляции переменными.
В этой статье мы рассмотрим одну из немного запутанных тем: if-предложения. Мы погрузимся в их работу и узнаем, как более точно использовать ее механизмы.
Примечание: эта статья о Bash, однако большая часть всего сказанного здесь применима к Zsh и другим оболочкам. Некоторые из вещей несовместимы с POSIX.
Основы
If-предложения в Bash очень похожи на аналогичные в других языках. Они следуют базовой форме:
[bash]if [[ "$some_variable" == "good input" ]]; then
echo "You got the right input."
elif [[ "$some_variable" == "ok input" ]]; then
echo "Close enough"
else
echo "No way Jose."
fi[/bash]
Дополнительный синтаксис в виде слова then, а также слегка странное закрывающее ключевое слово fi придает предложению немного экзотический вид, но в основе все то же самое, что и в других языках:
Если (if) что-то является истиной, тогда (then) выполни вот это. В противном случае проверяй другие условия по порядку и делай то же самое. Если ни одно из условий не сработало, выполни последнее указание.
Вы можете отбросить одну или все ветки elif, а также ветку else, если в них нет нужды!
Булевы операторы ! (нет), && (и), || (или) могут использоваться для комбинирования выражений, как и в других языках.
[bash]if [[ "$name" == "Ryan" ]] && ! [[ "$time" -lt 2000 ]]; then
echo "Sleeping"
elif [[ "$day" == "New Year’s Eve" ]] || [[ "$coffee_intake" -gt 9000 ]]; then
echo "Maybe awake"
else
echo "Probably sleeping"
fi[/bash]
Но здесь есть нечто запутанное. Порой вам встретятся двойные квадратные скобки, как в примере выше. А порой они будут одинарными.
[bash]if [ "$age" -gt 30 ]; then
echo "What an oldy."
fi[/bash]
Иногда могут быть и круглыми!
[bash]if (( age > 30 )); then
echo "Hey, 30 is the new 20, right?"
fi[/bash]
И временами их вообще не будет!
[bash]if is_upper "$1"; then
echo "Stop shouting at me."
fi[/bash]
Как знать, какие именно скобки нужно использовать? Когда каждый из вариантов является подходящим? Почему какие-то из них не будут работать в определенных случаях?
Что происходит на самом деле
Есть магические слова, которые стоят за работой if в Bash: коды выхода. Вот шаблон того, что происходит на самом деле:
[bash]if ANY_COMMAND_YOU_WANT_AT_ALL; then
# … stuff to do
fi[/bash]
Все верно: содержимое сразу после if может быть вообще любой командой, если она дает код выхода (а практически всегда так и бывает). Если команда возвращает код выхода 0 (в Bash это код успешно выполненной операции), тогда запускается код внутри ветки then. В противном случае Bash переходит к следующей ветке и делает новую попытку.
Но, погодите, это означает, что…
Ага. «[» это команда. Это, собственно, синтаксический сахар для встроенной команды test, которая проверяет и сравнивает переданные ей аргументы. «]» это на самом деле аргумент для команды [, который говорит ей прекратить проверять аргументы!
[bash]if [ "$price" -lt 10 ]; then
echo "What a deal!"
fi[/bash]
В этом примере команда [ принимает в качестве аргументов «$price» (переменная сразу же заменяется ее значением), -lt, 10 и ].
Теперь, с учетом того что -lt, -gt и похожие числовые сравнения на самом деле являются аргументами, не кажется ли странный синтаксис немного более осмысленным? Они выглядят, как опции! Вот почему знаки > и < странно себя ведут внутри одинарных квадратных скобок: Bash думает, что вы пытаетесь перенаправить input или output внутри команды!
Как насчет двойных квадратных скобок?
Довольно странно, однако [[ двойные квадратные скобки ]] и ((двойные круглые скобки)) это не совсем команды. Они представляют собой ключевые слова языка Bash, в результате чего они ведут себя немного более предсказуемо. Тем не менее, в зависимости от своего содержимого, они по-прежнему возвращают код выхода.
[[ Двойные квадратные скобки ]] работают в целом так же, как и [одинарные квадратные скобки], но имеют дополнительные возможности вроде лучшей поддержки регулярных выражений.
А двойные круглые скобки?
(( Двойные круглые скобки )) это конструкция, позволяющая осуществлять арифметические вычисления внутри Bash. Вам даже не нужно использовать их с if-предложением. Я часто ими пользуюсь, чтобы быстро инкрементировать счетчики и обновлять числовые переменные.
[bash]count=0
(( count++ ))
echo "$count"
# => 1
(( count += 4 ))
echo "$count"
# => 5[/bash]
Чего вы не видите, это того, что круглые скобки тоже при каждом запуске возвращают код выхода. Если результат в скобках равен нулю, возвращается код выхода 1 (по сути, нулевой результат это «ложь»). Любой другой результат считается истиной, при нем код выхода будет 0. Вот пример:
[bash]if (( -57 + 30 + 27 )); then
echo "First one"
elif (( 2 + 2 )); then
echo "Second one"
else
echo "Third one"
fi
# => "Second one"[/bash]
К счастью для нас, знаки «больше» и «меньше» внутри круглых скобок работают прекрасно. Если сравнение истинно, результат будет 1. В противном случае – 0.
[bash]if (( 5 > 3 )); then
echo "Numbers make sense."
elif (( 3 <= 2 )); then
echo "3 is less than or equal to 2. wat."
else
echo "Hwwaaa"
fi
# => "Numbers make sense"[/bash]
У этой функциональности есть странный, но любопытный побочный эффект:
[bash]echo $(( (5 > 3) + (0 == 0) ))
# => 2
# Each comparison is true, so we’re effectively echoing
# 1 + 1. Fun, right?[/bash]

Использование команд вместо скобок
Чаще всего в ваших if-предложениях будут использоваться скобки. Однако, поскольку можно использовать команды и их коды выхода, за вашими if-ами будет стоять вся мощь командной строки.
Скажем, мы хотим сделать что-то, но только в том случае, если в файле найдена определенная строка. Какую команду вы будете использовать для поиска текста в файлах? Grep!
[bash]echo "Hello, welcome to bean house." >> dialogue.txt
echo "Would you like some coffee?" >> dialogue.txt
if grep -q coffee dialogue.txt; then
echo "Found coffee, boss."
else
echo "No coffee."
fi
# => "Found coffee, boss."[/bash]
Если grep находит искомое, код выхода будет 0 (успех). Если нет, – 1. Мы используем это для запуска нашего if-предложения! Опция -q в grep означает —quiet. Она запрещает вывод найденных строк. Если мы ее уберем, то наш вывод будет выглядеть так:
[bash]Would you like some coffee?
Found coffee, boss.[/bash]
Использование ваших собственных функций
Самое лучшее в этом всем то, что вы можете написать собственные функции! Это поможет вам инкапсулировать вашу логику в нечто с высокоуровневым именем. Благодаря этому ваши скрипты станут более читаемыми, а ваши намерения – более понятными. И если есть что-то, где мы можем для примера использовать эту дополнительную ясность, это «выбрасывающий» скрипт, который Джерри написал в прошлом году и который мы до сих пор используем в сборочном конвейере.
Мне стоит написать отдельный пост о функциях в Bash, потому что они прекрасны и я их обожаю. А сейчас лишь отмечу, что эти функции работают как маленькие отдельные скрипты. Любые переданные им аргументы могут назначаться позиционно с помощью особых переменных $1, $2, $3 и т. д. Число аргументов содержится в переменной $#.
[bash]function is_number {
if [[ "$1" =~ ^[[:digit:]]+$ ]]; then
return 0
else
return 1
fi
}[/bash]
Обратите внимание на использование return вместо exit. Смысл тот же самый, за исключением того, что exit «убивает» весь скрипт, а не просто завершает функцию. Данная функция может использоваться вот так:
[bash]function is_number {
if [[ "$1" =~ ^[[:digit:]]+$ ]]; then
return 0
else
return 1
fi
}
age="$1"
if ! is_number "$age"; then
echo "Usage: dog_age NUMBER"
exit 1
fi[/bash]
Видите, как намерение программиста и логика становятся яснее без реализации деталей и написания громоздких регулярных выражений? Функции в Bash это Хорошо.
Фактически, если функция не возвращает (return) значение недвусмысленно, это возвращаемое значение последней команды в функции используется неявно, поэтому функцию можно сократить:
[bash]function is_number {
[[ "$1" =~ ^[[:digit:]]+$ ]]
}[/bash]
Если регулярное выражение сработает, код возврата двойных квадратных скобок будет нулевым, и таким образом функция возвращает 0. Если нет, и то, и другое возвращает 1. Это отличный способ давать имена регулярным выражениям.
If – это хорошая вещь
Вот и все! Применяйте полученные знания для чистки своих скриптов. Если у вас есть примеры использования if-предложений в Bash, которые могут быть полезны и другим людям, – поделитесь в комментариях!
Вот так, например, с помощью можно удалить все субдиректории, в которых меньше 13 файлов:
for dir in * ; do photos=$(ls ${dir} | wc -l); if ((photos < 13)); then rm -rf ${dir}; fi; done
(^__-)
помогло
Даже реальный слизняк лучше бы выглядел в превью, чем этот засушенный из папье маше
И это всё обучение башу? Халтурщина это.
Собственно, не bash-у, а использованию if в bash.