- How to represent multiple conditions in a shell if statement?
- 8 Answers 8
- If в Bash: от новичка до профессионала
- Основы
- Что происходит на самом деле
- Как насчет двойных квадратных скобок?
- А двойные круглые скобки?
- Использование команд вместо скобок
- Использование ваших собственных функций
- If – это хорошая вещь
How to represent multiple conditions in a shell if statement?
where line 15 is the one showing if . What is wrong with this condition? I guess something is wrong with the () .
You are not asking about shell conditions but test conditions. The whole expression in your example is evaluated by test ( [ ) and not by the shell. The shell evaluates just the exit status of [ .
8 Answers 8
Classic technique (escape metacharacters):
if [ \( "$g" -eq 1 -a "$c" = "123" \) -o \( "$g" -eq 2 -a "$c" = "456" \) ] then echo abc else echo efg fi
I’ve enclosed the references to $g in double quotes; that’s good practice, in general. Strictly, the parentheses aren’t needed because the precedence of -a and -o makes it correct even without them.
Note that the -a and -o operators are part of the POSIX specification for test , aka [ , mainly for backwards compatibility (since they were a part of test in 7th Edition UNIX, for example), but they are explicitly marked as ‘obsolescent’ by POSIX. Bash (see conditional expressions) seems to preempt the classic and POSIX meanings for -a and -o with its own alternative operators that take arguments.
With some care, you can use the more modern [[ operator, but be aware that the versions in Bash and Korn Shell (for example) need not be identical.
for g in 1 2 3 do for c in 123 456 789 do if [[ ( "$g" -eq 1 && "$c" = "123" ) || ( "$g" -eq 2 && "$c" = "456" ) ]] then echo "g = $g; c = $c; true" else echo "g = $g; c = $c; false" fi done done
Example run, using Bash 3.2.57 on Mac OS X:
g = 1; c = 123; true g = 1; c = 456; false g = 1; c = 789; false g = 2; c = 123; false g = 2; c = 456; true g = 2; c = 789; false g = 3; c = 123; false g = 3; c = 456; false g = 3; c = 789; false
You don’t need to quote the variables in [[ as you do with [ because it is not a separate command in the same way that [ is.
I would have thought so. However, there is another alternative, namely:
if [ "$g" -eq 1 -a "$c" = "123" ] || [ "$g" -eq 2 -a "$c" = "456" ] then echo abc else echo efg fi
Indeed, if you read the ‘portable shell’ guidelines for the autoconf tool or related packages, this notation — using ‘ || ‘ and ‘ && ‘ — is what they recommend. I suppose you could even go so far as:
if [ "$g" -eq 1 ] && [ "$c" = "123" ] then echo abc elif [ "$g" -eq 2 ] && [ "$c" = "456" ] then echo abc else echo efg fi
Where the actions are as trivial as echoing, this isn’t bad. When the action block to be repeated is multiple lines, the repetition is too painful and one of the earlier versions is preferable — or you need to wrap the actions into a function that is invoked in the different then blocks.
If в Bash: от новичка до профессионала
Перевод статьи «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 "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 и т. д. Число аргументов содержится в переменной $# .
Обратите внимание на использование 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) значение недвусмысленно, это возвращаемое значение последней команды в функции используется неявно, поэтому функцию можно сократить:
Если регулярное выражение сработает, код возврата двойных квадратных скобок будет нулевым, и таким образом функция возвращает 0. Если нет, и то, и другое возвращает 1. Это отличный способ давать имена регулярным выражениям.
If – это хорошая вещь
Вот и все! Применяйте полученные знания для чистки своих скриптов. Если у вас есть примеры использования if-предложений в Bash, которые могут быть полезны и другим людям, – поделитесь в комментариях!