Pierwszeństwo operatorów logicznych powłoki i&, ||


126

Próbuję zrozumieć, w jaki sposób pierwszeństwo operatora logicznego działa w trybie bash. Na przykład spodziewałbym się, że następujące polecenie niczego nie echa.

true || echo aaa && echo bbb

Jednak wbrew moim oczekiwaniom bbbdrukowane.

Czy ktoś może wyjaśnić, w jaki sposób mogę zrozumieć złożone &&i ||operatorów w bash?

Odpowiedzi:


127

W wielu językach komputerowych operatorzy o tym samym priorytecie są lewostronni . Oznacza to, że przy braku struktur grupujących operacje skrajnie lewe są wykonywane jako pierwsze. Bash nie jest wyjątkiem od tej reguły.

Jest to ważne, ponieważ w Bash &&i ||mają ten sam priorytet.

Więc w twoim przykładzie dzieje się to, że ||najpierw wykonuje się operację skrajnie po lewej ( ):

true || echo aaa

Ponieważ truejest to oczywiście prawda, ||zwarcie operatora i całe stwierdzenie jest uważane za prawdziwe bez potrzeby oceny echo aaazgodnie z oczekiwaniami. Teraz pozostaje wykonać najbardziej prawą operację:

(...) && echo bbb

Ponieważ pierwsza operacja została oceniona jako prawdziwa (tj. Miała status wyjścia 0), to tak, jakbyś wykonywał

true && echo bbb

więc &&nie będzie zwarcia, dlatego widzisz bbbecho.

Zyskasz takie samo zachowanie

false && echo aaa || echo bbb

Uwagi na podstawie komentarzy

  • Należy zauważyć, że zasada lewostronnego łączenia jest przestrzegana tylko wtedy, gdy oba operatory mają taki sam priorytet. Nie dzieje się tak, gdy używasz tych operatorów w połączeniu ze słowami kluczowymi takimi jak [[...]]lub ((...))lub używasz operatorów -oi -ajako argumentów poleceń testlub [. W takich przypadkach AND ( &&lub -a) ma pierwszeństwo przed OR ( ||lub -o). Dzięki komentarzowi Stephane Chazelas za wyjaśnienie tej kwestii.
  • Wydaje się, że w językach C i C &&ma wyższy priorytet niż ||prawdopodobnie dlatego oczekiwałeś, że zachowa się twój oryginalny konstrukt

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

    Nie dzieje się tak jednak w przypadku Bash, w którym oba operatory mają taki sam priorytet, dlatego Bash analizuje wyrażenie za pomocą reguły lewostronnego skojarzenia. Dzięki komentarzowi Kevina za poruszenie tego tematu.

  • Mogą również wystąpić przypadki, w których oceniane są wszystkie 3 wyrażenia. Jeśli pierwsze polecenie zwróci niezerowy status wyjścia, ||nie spowoduje zwarcia i wykona drugie polecenie. Jeśli drugie polecenie powróci ze statusem zerowego wyjścia, wówczas również &&nie nastąpi zwarcie, a trzecie polecenie zostanie wykonane. Dzięki komentarzowi Ignacio Vazquez-Abramsa za poruszenie tego tematu.


26
Uwaga, aby dodać trochę więcej zamieszania: podczas gdy operatory &&i ||powłoki jak w in cmd1 && cmd2 || cmd3mają taki sam priorytet, &&in ((...))i [[...]]mają pierwszeństwo przed ||( ((a || b && c))is ((a || (b && c)))). To samo dotyczy -a/ -ow test/ [a findi &/ |w expr.
Stéphane Chazelas

16
W językach podobnych do c &&ma wyższy priorytet niż ||, więc wystąpiłoby oczekiwane zachowanie PO. Nieświadomi użytkownicy przyzwyczajeni do takich języków mogą nie zdawać sobie sprawy z tego, że w bash mają taki sam priorytet, więc może warto wskazać bardziej wyraźnie, że mają taki sam priorytet w bash.
Kevin

Zauważ też, że zdarzają się sytuacje, w których można uruchomić wszystkie trzy polecenia, tj. Czy polecenie środkowe może zwrócić wartość true lub false.
Ignacio Vazquez-Abrams

@Kevin Sprawdź poprawioną odpowiedź.
Joseph R.

1
Dzisiaj dla mnie najpopularniejszym hitem Google dla „pierwszeństwa operatora bash” jest tldp.org/LDP/abs/html/opprecedence.html ... który twierdzi, że && ma wyższy priorytet niż ||. Jeszcze @JosephR. jest oczywiście słuszny de facto i de jure. Dash zachowuje się tak samo i szukając „pierwszeństwa” w pubs.opengroup.org/onlinepubs/009695399/utilities/… , widzę, że jest to wymóg POSIX, więc możemy na nim polegać. Do zgłaszania błędów znalazłem tylko adres e-mail autora, który z pewnością został spamowany na śmierć przez ostatnie sześć lat. W każdym razie spróbuję ...
Martin Dorey

62

Jeśli chcesz, aby wiele rzeczy zależało od twojego stanu, zgrupuj je:

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

To nic nie drukuje

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

wypisuje oba ciągi.


Powód, dla którego tak się dzieje, jest o wiele prostszy niż to, co Joseph rozpoznaje. Pamiętaj, co Bash robi z ||i &&. Chodzi o status powrotu poprzedniego polecenia. Dosłowny sposób patrzenia na twoje surowe polecenie to:

( true || echo aaa ) && echo bbb

Pierwsze polecenie ( true || echo aaa) kończy się za pomocą 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 Za osiągnięcie zamierzonego wyniku PO. Pamiętaj, że nawiasy, które umieściłeś, (true || echo aaa) && echo bbbsą dokładnie tym, co robię.
Joseph R.

1
Miło widzieć @Oli w tej części świata.
Braiam

7
Zwróć uwagę na półkolumnę ;na samym końcu. Bez tego nie będzie działać!
Serge Stroobandt,

2
Miałem z tym kłopoty, ponieważ brakowało mi średnika końcowego po ostatnim poleceniu (np. echo bbb;W pierwszych przykładach). Kiedy zorientowałem się, że mi tego brakuje, ta odpowiedź była dokładnie tym, czego szukałem. +1 za pomoc w wymyśleniu, jak osiągnąć to, czego chciałem!
Doktor J

5
Warto przeczytać dla wszystkich zdezorientowanych (...)i {...}notacji: instrukcja grupowania poleceń
ANTARA

16

Te &&i ||operatorzy nie dokładna inline zamienniki do if-then-else. Choć stosowane ostrożnie, mogą osiągnąć to samo.

Pojedynczy test jest prosty i jednoznaczny ...

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

Jednak próba dodania wielu testów może dać nieoczekiwane wyniki ...

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

Dlaczego echo zarówno FAŁSZ, jak i PRAWDA?

To, co się tutaj dzieje, polega na tym, że nie zdawaliśmy sobie z tego sprawy &&i ||są przeciążonymi operatorami, które działają inaczej w nawiasach testu warunkowego [[ ]]niż na liście AND i OR (wykonanie warunkowe), którą tutaj mamy.

Z strony podręcznika bash (edytowane) ...

Listy

Lista jest sekwencją jednego lub więcej potoków oddzielonych przez jednego z operatorów;, &, && lub ││ i opcjonalnie zakończonych przez jeden z;, & lub. Z tych operatorów list, && i ││ mają równy priorytet, a następnie; i &, które mają równy priorytet.

Sekwencja jednego lub więcej znaków nowego wiersza może pojawić się na liście zamiast średnika w celu rozgraniczenia poleceń.

Jeśli polecenie zostanie zakończone przez operatora sterującego &, powłoka wykonuje polecenie w tle w podpowłoce. Powłoka nie czeka na zakończenie polecenia, a zwracanym statusem jest 0. Polecenia oddzielone znakiem a; są wykonywane sekwencyjnie; powłoka czeka na zakończenie każdego polecenia. Status powrotu to status wyjścia ostatniego wykonanego polecenia.

Listy AND i OR są sekwencjami jednego lub więcej potoków oddzielonych odpowiednio przez operatory sterujące && i ││. Listy AND i OR są wykonywane z lewą asocjatywnością.

Lista AND ma postać ...
command1 && command2
Polecenie2 jest wykonywane wtedy i tylko wtedy, gdy polecenie1 zwraca status wyjścia równy zero.

Lista OR ma postać ...
command1 ││ command2
Komenda2 jest wykonywana tylko wtedy, gdy komenda1 zwraca niezerowy status wyjścia.

Status powrotu list AND i OR to status wyjścia ostatniego polecenia wykonanego na liście.

Wracając do naszego ostatniego przykładu ...

[[ 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"

W porządku. Jeśli to prawda, to dlaczego w ogóle przedostatni przykład w ogóle coś przypomina?

[[ 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"

Ryzyko błędów logicznych podczas używania więcej niż jednego &&lub ||na liście poleceń jest dość wysokie.

Rekomendacje

Pojedynczy &&lub ||na liście poleceń działa zgodnie z oczekiwaniami, więc jest całkiem bezpieczny. Jeśli jest to sytuacja, w której nie potrzebujesz klauzuli else, łatwiej jest zastosować coś takiego jak następujące (nawiasy klamrowe są wymagane do zgrupowania ostatnich 2 poleceń) ...

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

Wiele operatorów &&i ||operatory, w których każde polecenie oprócz ostatniego jest testem (tj. W nawiasach [[ ]]), są zwykle również bezpieczne, ponieważ wszyscy operatorzy oprócz ostatniego zachowują się zgodnie z oczekiwaniami. Ostatni operator działa bardziej jak klauzula thenlub else.


0

Byłem również zdezorientowany tym, ale oto, jak myślę o sposobie, w jaki Bash czyta twoje oświadczenie (ponieważ odczytuje symbole od lewej do prawej):

  1. Znaleziono symbol true. Należy to ocenić po osiągnięciu końca polecenia. W tym momencie nie wiem, czy ma jakieś argumenty. Zapisz polecenie w buforze wykonawczym.
  2. Znaleziono symbol ||. Poprzednie polecenie zostało zakończone, więc oceń je. Command (bufor) wykonywany: true. Wynik oceny: 0 (tzn. Sukces). Zapisz wynik 0 w rejestrze „ostatniej oceny”. Teraz rozważ ||sam symbol . Zależy to od wyniku niezerowej ostatniej oceny. Sprawdzono rejestr „ostatniej oceny” i znaleziono 0. Ponieważ 0 nie jest niezerowe, poniższe polecenie nie musi być oceniane.
  3. Znaleziono symbol echo. Może zignorować ten symbol, ponieważ następujące polecenie nie musiało być oceniane.
  4. Znaleziono symbol aaa. Jest to argument dla polecenia echo(3), ale ponieważ echo(3) nie musiał być oceniany, można go zignorować.
  5. Znaleziono symbol &&. Zależy to od wyniku ostatniej oceny wynoszącej zero. Sprawdzono rejestr „ostatniej oceny” i znaleziono 0. Ponieważ 0 to zero, należy ocenić następujące polecenie.
  6. Znaleziono symbol echo. To polecenie musi zostać ocenione po osiągnięciu końca polecenia, ponieważ następujące polecenie musiało zostać ocenione. Zapisz polecenie w buforze wykonawczym.
  7. Znaleziono symbol bbb. To jest argument do polecenia echo(6). Ponieważ echotrzeba było to ocenić, dodaj bbbdo bufora wykonywania.
  8. Osiągnięto koniec linii. Poprzednie polecenie jest teraz kompletne i wymagało oceny. Command (bufor) wykonywany: echo bbb. Wynik oceny: 0 (tzn. Sukces). Zapisz wynik 0 w rejestrze „ostatniej oceny”.

I oczywiście ostatni krok powoduje bbbwyświetlenie echa na konsoli.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.