Перевод статьи «Bash If Statements: Beginner to Advanced».
Bash это интересный язык. Изначально он был создан для интерактивного использования в командной строке, главным образом в REPL (Read-Evaluate-Print-Loop – «цикл чтение-вычисление-вывод») для работы над одной командой пользователя за раз.
Этот язык спроектирован для существенной, но при этом лаконичной обработки простого текста. У этого есть побочный эффект: немного сложнее выполнять вещи, которые не являются интерактивными или связанными с текстом, например, арифметические вычисления, контроль потоков и манипуляции переменными.
В этой статье мы рассмотрим одну из немного запутанных тем: if-предложения. Мы погрузимся в их работу и узнаем, как более точно использовать ее механизмы.
Примечание: эта статья о Bash, однако большая часть всего сказанного здесь применима к Zsh и другим оболочкам. Некоторые из вещей несовместимы с POSIX.
Основы
If-предложения в 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
Дополнительный синтаксис в виде слова then
, а также слегка странное закрывающее ключевое слово fi
придает предложению немного экзотический вид, но в основе все то же самое, что и в других языках:
Если (if) что-то является истиной, тогда (then) выполни вот это. В противном случае проверяй другие условия по порядку и делай то же самое. Если ни одно из условий не сработало, выполни последнее указание.
Вы можете отбросить одну или все ветки elif
, а также ветку else
, если в них нет нужды!
Булевы операторы !
(нет), &&
(и), ||
(или) могут использоваться для комбинирования выражений, как и в других языках.
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
Но здесь есть нечто запутанное. Порой вам встретятся двойные квадратные скобки, как в примере выше. А порой они будут одинарными.
if [ "$age" -gt 30 ]; then echo "What an oldy." fi
Иногда могут быть и круглыми!
if (( age > 30 )); then echo "Hey, 30 is the new 20, right?" fi
И временами их вообще не будет!
if is_upper "$1"; then echo "Stop shouting at me." fi
Как знать, какие именно скобки нужно использовать? Когда каждый из вариантов является подходящим? Почему какие-то из них не будут работать в определенных случаях?
Что происходит на самом деле
Есть магические слова, которые стоят за работой if
в Bash: коды выхода. Вот шаблон того, что происходит на самом деле:
if ANY_COMMAND_YOU_WANT_AT_ALL; then # ... stuff to do fi
Все верно: содержимое сразу после if
может быть вообще любой командой, если она дает код выхода (а практически всегда так и бывает). Если команда возвращает код выхода 0 (в Bash это код успешно выполненной операции), тогда запускается код внутри ветки then
. В противном случае Bash переходит к следующей ветке и делает новую попытку.
Но, погодите, это означает, что…
Ага. «[» это команда. Это, собственно, синтаксический сахар для встроенной команды test
, которая проверяет и сравнивает переданные ей аргументы. «]» это на самом деле аргумент для команды [
, который говорит ей прекратить проверять аргументы!
if [ "$price" -lt 10 ]; then echo "What a deal!" fi
В этом примере команда [
принимает в качестве аргументов «$price» (переменная сразу же заменяется ее значением), -lt
, 10
и ]
.
Теперь, с учетом того что -lt
, -gt
и похожие числовые сравнения на самом деле являются аргументами, не кажется ли странный синтаксис немного более осмысленным? Они выглядят, как опции! Вот почему знаки >
и <
странно себя ведут внутри одинарных квадратных скобок: Bash думает, что вы пытаетесь перенаправить input или output внутри команды!
Как насчет двойных квадратных скобок?
Довольно странно, однако [[
двойные квадратные скобки ]]
и ((
двойные круглые скобки))
это не совсем команды. Они представляют собой ключевые слова языка Bash, в результате чего они ведут себя немного более предсказуемо. Тем не менее, в зависимости от своего содержимого, они по-прежнему возвращают код выхода.
[[
Двойные квадратные скобки ]]
работают в целом так же, как и [
одинарные квадратные скобки]
, но имеют дополнительные возможности вроде лучшей поддержки регулярных выражений.
А двойные круглые скобки?
((
Двойные круглые скобки ))
это конструкция, позволяющая осуществлять арифметические вычисления внутри Bash. Вам даже не нужно использовать их с if-предложением. Я часто ими пользуюсь, чтобы быстро инкрементировать счетчики и обновлять числовые переменные.
count=0 (( count++ )) echo "$count" # => 1 (( count += 4 )) echo "$count" # => 5
Чего вы не видите, это того, что круглые скобки тоже при каждом запуске возвращают код выхода. Если результат в скобках равен нулю, возвращается код выхода 1 (по сути, нулевой результат это «ложь»). Любой другой результат считается истиной, при нем код выхода будет 0. Вот пример:
if (( -57 + 30 + 27 )); then echo "First one" elif (( 2 + 2 )); then echo "Second one" else echo "Third one" fi # => "Second one"
К счастью для нас, знаки «больше» и «меньше» внутри круглых скобок работают прекрасно. Если сравнение истинно, результат будет 1. В противном случае – 0.
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"
У этой функциональности есть странный, но любопытный побочный эффект:
echo $(( (5 > 3) + (0 == 0) )) # => 2 # Each comparison is true, so we're effectively echoing # 1 + 1. Fun, right?
Использование команд вместо скобок
Чаще всего в ваших if-предложениях будут использоваться скобки. Однако, поскольку можно использовать команды и их коды выхода, за вашими if-ами будет стоять вся мощь командной строки.
Скажем, мы хотим сделать что-то, но только в том случае, если в файле найдена определенная строка. Какую команду вы будете использовать для поиска текста в файлах? Grep!
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."
Если grep находит искомое, код выхода будет 0 (успех). Если нет, – 1. Мы используем это для запуска нашего if-предложения! Опция -q
в grep означает --quiet
. Она запрещает вывод найденных строк. Если мы ее уберем, то наш вывод будет выглядеть так:
Would you like some coffee? Found coffee, boss.
Использование ваших собственных функций
Самое лучшее в этом всем то, что вы можете написать собственные функции! Это поможет вам инкапсулировать вашу логику в нечто с высокоуровневым именем. Благодаря этому ваши скрипты станут более читаемыми, а ваши намерения – более понятными. И если есть что-то, где мы можем для примера использовать эту дополнительную ясность, это «выбрасывающий» скрипт, который Джерри написал в прошлом году и который мы до сих пор используем в сборочном конвейере.
Мне стоит написать отдельный пост о функциях в Bash, потому что они прекрасны и я их обожаю. А сейчас лишь отмечу, что эти функции работают как маленькие отдельные скрипты. Любые переданные им аргументы могут назначаться позиционно с помощью особых переменных $1
, $2
, $3
и т. д. Число аргументов содержится в переменной $#
.
function is_number { if [[ "$1" =~ ^[[:digit:]]+$ ]]; then return 0 else return 1 fi }
Обратите внимание на использование return вместо exit. Смысл тот же самый, за исключением того, что exit «убивает» весь скрипт, а не просто завершает функцию. Данная функция может использоваться вот так:
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 это Хорошо.
Фактически, если функция не возвращает (return) значение недвусмысленно, это возвращаемое значение последней команды в функции используется неявно, поэтому функцию можно сократить:
function is_number { [[ "$1" =~ ^[[:digit:]]+$ ]] }
Если регулярное выражение сработает, код возврата двойных квадратных скобок будет нулевым, и таким образом функция возвращает 0. Если нет, и то, и другое возвращает 1. Это отличный способ давать имена регулярным выражениям.
If – это хорошая вещь
Вот и все! Применяйте полученные знания для чистки своих скриптов. Если у вас есть примеры использования if-предложений в Bash, которые могут быть полезны и другим людям, – поделитесь в комментариях!
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]
Вот так, например, с помощью можно удалить все субдиректории, в которых меньше 13 файлов:
for dir in * ; do photos=$(ls ${dir} | wc -l); if ((photos < 13)); then rm -rf ${dir}; fi; done
(^__-)
помогло
Даже реальный слизняк лучше бы выглядел в превью, чем этот засушенный из папье маше
И это всё обучение башу? Халтурщина это.
Собственно, не bash-у, а использованию if в bash.
Эта статья для меня многое прояснила. Спасибо большое!
Пожалуйста:) Мы рады, что нам удается находить полезные людям статьи.
> Если (if) что-то является истиной, тогда (then) выполни вот это.
> Если grep находит искомое, код выхода будет 0 (успех). Если нет, – 1. Мы используем это для запуска нашего if-предложения!
Как сочетаются эти два утверждения? Разве ноль не False, а единица не True?
Тогда в случае с grep не должно ли быть:
if ! grep -q coffee dialogue.txt; then
В bash 0 — истина, 1 — ложь.
Сделано так потому, что по сути это не булевы значения, а коды возврата. 0 — успех, а все, что не ноль — ошибка. Код возврата ошибки может быть 1-255. А успех — просто успех, ничего дополнительно о нем знать не нужно
Здравствуйте! Как в if…then…elif…then выполнить elif сравнивая его с предыдущем значением true, например если (в цикле) в if…then получаются false false true, то последующий elif сравнивал 1 с true и после выполнялся then?
Прошу, раскрой тему, я не совсем понял
Сппасбо Огромное! за статью!Всё очень доступно и понятно!
Благодарю!