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


126

Я намагаюся зрозуміти, як працює пріоритет логічного оператора в bash. Наприклад, я б очікував, що наступна команда нічого не повторює.

true || echo aaa && echo bbb

Однак, всупереч моєму сподіванню, bbbдрукується.

Може хтось, будь ласка, пояснить, як я можу зрозуміти складних &&та ||операторів у баші?

Відповіді:


127

У багатьох комп'ютерних мовах оператори з однаковим пріоритетом є ліво-асоціативними . Тобто, за відсутності групувальних структур, перші ліві операції виконуються спочатку. Баш - не виняток із цього правила.

Це важливо , тому що, в Bash, &&і ||мають однаковий пріоритет.

Отже, у вашому прикладі відбувається те ||, що перша ліва операція ( ) виконується першою:

true || echo aaa

Оскільки trueце, очевидно, вірно, ||оператор короткого замикання і весь вислів вважається істинним без необхідності оцінювати так, echo aaaяк ви очікували. Тепер залишається зробити найправішу операцію:

(...) && echo bbb

Оскільки перша операція була оцінена як істина (тобто мала статус виходу 0), це ніби виконується

true && echo bbb

тому &&волевиявлення не буде короткого замикання, саме тому ви бачите bbbвідлуння.

Ви б отримали таку саму поведінку

false && echo aaa || echo bbb

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

  • Слід зазначити, що правило лівої асоціативності виконується лише тоді, коли обидва оператори мають однаковий пріоритет. Це не той випадок , коли ви використовуєте ці оператори в поєднанні з такими ключовими словами, як [[...]]або ((...))або використовувати -oі -aоператор в якості аргументів testабо [команд. У таких випадках AND ( &&або -a) має перевагу над OR ( ||або -o). Завдяки коментарю Стефана Шазеласа за уточнення цього пункту.
  • Здається, що в мовах С та С &&є перевагу, ніж ||це, ймовірно, тому ви очікували, що ваша оригінальна конструкція поводитиметься так

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

    Однак це не так з Bash, коли обидва оператори мають однаковий пріоритет, саме тому Bash аналізує ваше вираження, використовуючи правило лівої асоціативності. Завдяки коментарю Кевіна за те, що це підняло.

  • Можуть також бути випадки, коли оцінюються всі 3 вирази. Якщо перша команда повертає ненульовий статус виходу, ||не буде короткого замикання і переходить до виконання другої команди. Якщо друга команда повернеться з нульовим статусом виходу, то &&коротке замикання також не буде, і третя команда буде виконана. Завдяки коментарю Ігнасіо Васкеса-Абрамса за те, що це було зроблено.


26
Зауважте, що додано трохи більше плутанини: хоча оператори &&і ||оболонки, як і у них, cmd1 && cmd2 || cmd3мають однаковий пріоритет, &&в ((...))і [[...]]має перевагу над ||( ((a || b && c))є ((a || (b && c)))). Те саме стосується -a/ -oв test/ [і findта &/ |в expr.
Стефан Шазелас

16
У c-подібних мовах &&перевага є більшою, ніж ||очікувана поведінка ОП. Користувачі, що не користуються такою мовою, можуть не усвідомлювати, що в bash вони мають однаковий пріоритет, тому, можливо, варто чіткіше зазначити, що вони мають однаковий пріоритет у bash.
Кевін

Також зауважте, що є ситуації, коли всі три команди можуть бути запущені, тобто якщо середня команда може повернути істинну, або хибну.
Ігнасіо Васкес-Абрамс

@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; }

друкує обидва рядки.


Причина цього відбувається набагато простіше, ніж Йосип робить. Згадайте, що робить Баш із ||та &&. Це все про стан повернення попередньої команди. Буквальний спосіб перегляду вашої необробленої команди:

( 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

7
+1 Для досягнення наміченого результату ОП. Зауважте, що дужки, які ви помістили, (true || echo aaa) && echo bbbце саме те, що я оформлюю.
Джозеф Р.

1
Приємно бачити @Oli в цьому куточку світу.
Брайам

7
Зверніть увагу на напівколонку ;в самому кінці. Без цього не вийде!
Серж Стротобандт

2
У мене виникли проблеми з цим, тому що мені не вистачало запятої крапки після останньої команди (наприклад, echo bbb;у перших прикладах). Як тільки я зрозумів, що мені це не вистачає, ця відповідь була саме тим, що я шукав. +1 за те, щоб допомогти мені зрозуміти, як досягти того, що я хотів!
Doktor J

5
Варто прочитати для всіх, хто плутається (...)та {...}позначає: посібник з групування команд
ANTARA

16

Оператори &&та ||оператори не є точними замінниками вбудованих даних для if-то-else Хоча якщо їх застосовувати обережно, вони можуть зробити те саме.

Єдиний тест простий і однозначний ...

[[ 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?)

Чому і ЛЖЕ, і ІСТИНА перегукуються?

Тут відбувається те, що ми не усвідомили цього &&і ||перевантажені операторами, які діють інакше в умовних тестових дужках, [[ ]]ніж у списку І та АБО (умовне виконання), який ми маємо тут.

З баш-сторінки (відредаговано) ...

Списки

Список - це послідовність одного або декількох трубопроводів, розділених одним з операторів;, &, && або ││ і необов'язково закінчується одним із;, &, або. З цих операторів списку && та ││ мають однаковий пріоритет, за яким слідують; та &, які мають рівний пріоритет.

Послідовність одного або декількох нових рядків може з’являтися в списку замість крапки з комою для розмежування команд.

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

Списки 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"

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

Рекомендації

Сингл &&або ||в списку команд працює так, як очікувалося, тому досить безпечно. Якщо це ситуація, коли ви не потребуєте іншого пункту, щось більш чітке можна виконати (наприклад, фігурні дужки потрібні для групування двох останніх команд) ...

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

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


0

Я також збентежився цим, але ось як я думаю про те, як Баш читає ваше твердження (як він читає символи зліва направо):

  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відлуння до консолі.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.