Приоритет логических операторов оболочки &&, ||

126

Я пытаюсь понять, как работает приоритет логического оператора в Bash. Например, я ожидал, что следующая команда ничего не отображает.

true || echo aaa && echo bbb

Однако, вопреки моим ожиданиям, bbbпечатается.

Может кто-нибудь объяснить, как я могу понять составные &&и ||операторы в Bash?

Мартин Вегтер
источник

Ответы:

127

Во многих компьютерных языках операторы с одинаковым приоритетом являются левоассоциативными . То есть при отсутствии группирующих структур самые левые операции выполняются первыми. Bash не является исключением из этого правила.

Это важно, потому что в Bash &&и ||имеют такой же приоритет.

Итак, что происходит в вашем примере, это то, что крайняя левая операция ( ||) выполняется первой:

true || echo aaa

Поскольку true, очевидно, это правда, ||оператор замыкает накоротко и весь оператор считается верным без необходимости оценивать, echo aaaкак вы ожидаете. Теперь осталось сделать самую правую операцию:

(...) && echo bbb

Поскольку первая операция имеет значение true (т. Е. Имеет состояние выхода 0), это как если бы вы выполняли

true && echo bbb

так что &&не будет короткое замыкание, поэтому вы видите bbbотражение.

Вы бы получили такое же поведение с

false && echo aaa || echo bbb

Примечания на основе комментариев

  • Следует отметить, что правило левой ассоциативности соблюдается только в том случае, если оба оператора имеют одинаковый приоритет. Это не тот случай , когда вы используете эти операторы в сочетании с такими ключевыми словами, как [[...]]или ((...))или использовать -oи -aоператор в качестве аргументов testили [команд. В таких случаях AND ( &&или -a) имеет приоритет над OR ( ||или -o). Спасибо комментарию Стефана Чазеласа за разъяснение этого вопроса.
  • Похоже, что в C и C-подобных языках &&приоритет выше, чем, ||вероятно, поэтому вы ожидали, что ваша оригинальная конструкция будет вести себя как

    true || (echo aaa && echo bbb). 

    Однако это не относится к Bash, в котором оба оператора имеют одинаковый приоритет, поэтому Bash анализирует ваше выражение, используя правило левой ассоциативности. Спасибо за комментарий Кевина за то, что он поднял этот вопрос.

  • Также могут быть случаи, когда все 3 выражения оцениваются. Если первая команда возвращает ненулевой статус выхода, ||не будет короткого замыкания и продолжит выполнение второй команды. Если вторая команда возвращается с нулевым статусом выхода, то также &&не будет происходить короткое замыкание, и будет выполнена третья команда. Спасибо комментарию Игнасио Васкеса-Абрамса за то, что он поднял этот вопрос.

Джозеф Р.
источник
26
Крошечную записку , чтобы добавить немного больше путаницы: в то время как &&и ||операторы оболочки , как и в cmd1 && cmd2 || cmd3имеют одинаковый приоритет, то &&по прибытии ((...))и [[...]]имеет приоритет над ||( ((a || b && c))есть ((a || (b && c)))). То же самое касается -a/ -oв test/ [и findи &/ |в expr.
Стефан Шазелас
16
В с-подобных языках &&имеет более высокий приоритет, чем ||, поэтому ожидаемое поведение OP будет иметь место. Неосторожные пользователи, привыкшие к таким языкам, могут не осознавать, что в bash они имеют одинаковый приоритет, поэтому стоит более четко указать, что они имеют такой же приоритет в bash.
Кевин
Также обратите внимание, что существуют ситуации, когда все три команды могут быть выполнены, т.е. если средняя команда может вернуть либо true, либо false.
Игнасио Васкес-Абрамс
@Kevin Пожалуйста, проверьте отредактированный ответ.
Джозеф Р.
1
Сегодня для меня самый популярный запрос Google о «приоритете операторов bash» - это tldp.org/LDP/abs/html/opprecedence.html ... который утверждает, что && имеет более высокий приоритет, чем ||. Еще @JosephR. явно прав де-факто и де-юре тоже. Dash ведет себя так же и ищет «приоритет» в pubs.opengroup.org/onlinepubs/009695399/utilities/… Я вижу, что это требование POSIX, поэтому мы можем зависеть от него. Все, что я нашел для сообщения об ошибках, был адрес электронной почты автора, который, несомненно, был спамом за последние шесть лет. Я все равно попробую ...
Мартин Дори
62

Если вы хотите, чтобы несколько вещей зависели от вашего состояния, сгруппируйте их:

true || { echo aaa && echo bbb; }

Это ничего не печатает, в то время как

true && { echo aaa && echo bbb; }

печатает обе строки.


Причина, по которой это происходит, намного проще, чем это делает Джозеф. Помните, что Bash делает с ||и &&. Все дело в статусе возврата предыдущей команды. Буквальный взгляд на вашу необработанную команду:

( true || echo aaa ) && echo bbb

Первая команда ( true || echo aaa) завершается с 0.

$ true || echo aaa; echo $?
0
$ true && echo aaa; echo $?
aaa
0

$ false && echo aaa; echo $?
1
$ false || echo aaa; echo $?
aaa
0
Oli
источник
7
+1 За достижение ожидаемого результата ОП. Обратите внимание, что круглые скобки, которые вы поместили, (true || echo aaa) && echo bbbименно то, что я делаю.
Джозеф Р.
1
Приятно видеть @Oli в этой части света.
Брайам
7
Обратите внимание на полуколонну ;в самом конце. Без этого не получится!
Серж Строобандт
2
У меня были проблемы с этим, потому что я пропускал конечную точку с запятой после последней команды (например, echo bbb;в первых примерах). Как только я понял, что мне не хватает этого, этот ответ был именно тем, что я искал. +1 за помощь в выяснении, как добиться того, чего я хотел!
Доктор J
5
Стоит прочитать всем, кого смущают (...)и {...}обозначения: руководство по группированию команд
ANTARA
16

Эти &&и ||операторы не точные встроенные замены для если-то-иначе. Хотя, если их использовать осторожно, они могут сделать почти то же самое.

Один тест прост и однозначен ...

[[ A == A ]]  && echo TRUE                          # TRUE
[[ A == B ]]  && echo TRUE                          # 
[[ A == A ]]  || echo FALSE                         # 
[[ A == B ]]  || echo FALSE                         # FALSE

Однако попытка добавить несколько тестов может привести к неожиданным результатам ...

[[ A == A ]]  && echo TRUE   || echo FALSE          # TRUE  (as expected)
[[ A == B ]]  && echo TRUE   || echo FALSE          # FALSE (as expected)
[[ A == A ]]  || echo FALSE  && echo TRUE           # TRUE  (as expected)
[[ A == B ]]  || echo FALSE  && echo TRUE           # FALSE TRUE   (huh?)

Почему отражаются как ЛОЖЬ, так и ИСТИНА?

Здесь происходит то, что мы не осознали этого &&и ||являемся перегруженными операторами, которые действуют внутри скобок условных тестов иначе, [[ ]]чем в списках AND и OR (условное выполнение), которые мы здесь имеем.

Из справочной страницы bash (отредактировано) ...

Списки

Список - это последовательность из одного или нескольких конвейеров, разделенных одним из операторов;, &, && или ││ и, возможно, завершенных одним из;, &, или. Из этих операторов списка && и ││ имеют одинаковый приоритет, после чего следует; и &, которые имеют равный приоритет.

Последовательность из одного или нескольких символов новой строки может появиться в списке вместо точки с запятой для разделения команд.

Если команда завершается оператором управления &, оболочка выполняет команду в фоновом режиме в подоболочке. Оболочка не ожидает завершения команды, и возвращается статус 0. Команды, разделенные символом a; выполняются последовательно; оболочка ожидает завершения каждой команды по очереди. Статус возврата - это статус выхода последней выполненной команды.

Списки AND и OR являются последовательностями одного из нескольких конвейеров, разделенных управляющими операторами && и, соответственно. Списки AND и OR выполняются с левой ассоциативностью.

Список AND имеет вид ...
command1 && command2
Command2 выполняется тогда и только тогда, когда command1 возвращает нулевое состояние выхода.

Список ИЛИ имеет форму ...
command1 ││ command2
Command2 выполняется тогда и только тогда, когда command1 возвращает ненулевой статус выхода.

Статус возврата списков AND и OR - это статус выхода последней команды, выполненной в списке.

Возвращаясь к нашему последнему примеру ...

[[ A == B ]]  || echo FALSE  && echo TRUE

[[ A == B ]]  is false

     ||       Does NOT mean OR! It means...
              'execute next command if last command return code(rc) was false'

 echo FALSE   The 'echo' command rc is always true
              (i.e. it successfully echoed the word "FALSE")

     &&       Execute next command if last command rc was true

 echo TRUE    Since the 'echo FALSE' rc was true, then echo "TRUE"

Хорошо. Если это правильно, то почему следующий к последнему примеру что-нибудь эхо?

[[ A == A ]]  || echo FALSE  && echo TRUE


[[ A == A ]]  is true

     ||       execute next command if last command rc was false.

 echo FALSE   Since last rc was true, shouldn't it have stopped before this?
                Nope. Instead, it skips the 'echo FALSE', does not even try to
                execute it, and continues looking for a `&&` clause.

     &&       ... which it finds here

 echo TRUE    ... so, since `[[ A == A ]]` is true, then it echos "TRUE"

Риск логических ошибок при использовании более одной &&или ||в списке команд довольно высок.

рекомендации

Один &&или ||в списке команд работает, как ожидалось, так что это довольно безопасно. Если это ситуация, когда вам не нужно выражение else, может быть понятнее что-то вроде следующего (фигурные скобки необходимы для группировки последних 2 команд) ...

[[ $1 == --help ]]  && { echo "$HELP"; exit; }

Множественные операторы &&и ||операторы, где каждая команда, кроме последней, является тестом (т. Е. Внутри скобок [[ ]]), обычно также безопасны, поскольку все, кроме последнего оператора, ведут себя так, как ожидается. Последний оператор действует больше как предложение thenили else.

DocSalvager
источник
0

Я также смутился из-за этого, но вот как я думаю о том, как Bash читает ваше утверждение (как оно читает символы слева направо):

  1. Найдено символ true. Это нужно будет оценить, как только будет достигнут конец команды. На данный момент, не знаю, есть ли у него какие-либо аргументы. Сохраните команду в буфере выполнения.
  2. Найдено символ ||. Предыдущая команда завершена, поэтому оцените ее. Команда (буфер) выполняется true. Результат оценки: 0 (т.е. успех). Сохранить результат 0 в регистре «последней оценки». Теперь рассмотрим ||сам символ . Это зависит от того, что результат последней оценки не равен нулю. Проверил регистр «последней оценки» и нашел 0. Поскольку 0 не является ненулевым, следующую команду оценивать не нужно.
  3. Найдено символ echo. Может игнорировать этот символ, потому что следующую команду не нужно было оценивать.
  4. Найдено символ aaa. Это аргумент команды echo(3), но, поскольку echo(3) оценивать не нужно, его можно игнорировать.
  5. Найдено символ &&. Это зависит от того, что результат последней оценки равен нулю. Проверил регистр «последней оценки» и нашел 0. Поскольку 0 - ноль, необходимо выполнить следующую команду.
  6. Найдено символ echo. Эта команда должна быть оценена, как только достигнут конец команды, потому что следующая команда должна была быть оценена. Сохраните команду в буфере выполнения.
  7. Найдено символ bbb. Это аргумент команды echo(6). Так echoкак нужно было оценить, добавить bbbв буфер выполнения.
  8. Достигнут конец строки. Предыдущая команда завершена, и ее необходимо оценить. Команда (буфер) выполняется echo bbb. Результат оценки: 0 (т.е. успех). Сохранить результат 0 в регистре «последней оценки».

И, конечно же, последний шаг вызывает bbbвывод на консоль.

Kidburla
источник