Pierwszeństwo Pipe (|) oraz logiczne i (&&) w bash

17

W klasycznym scenariuszu z priorytetem operatora masz taką linię:

(cd ~/screenshots/ && ls screenshot* | head -n 5)

I nie wiesz, czy jest parsowany ((A && B) | C)czy (A && B | C)...

Prawie oficjalna dokumentacja znaleźć tutaj nie wymienić rurę na liście, więc nie można po prostu sprawdzić w tabeli.

Ponadto w bash (nie służy tylko do zmiany kolejności operacji, ale tworzy podpowłokę , więc nie jestem w 100% pewien, że te linie są odpowiednikiem poprzedniej linii:

((cd ~/screenshots/ && ls screenshot*) | head -n 5)

Mówiąc bardziej ogólnie, jak poznać AST linii bash? W Pythonie mam funkcję, która daje mi drzewo, dzięki czemu mogę łatwo dwukrotnie sprawdzić kolejność działania.

Robert Vanden Eynde
źródło
1
Może pomóc wiedzieć, że |jest to tylko złącze, które pasuje do standardowego złącza LHS do standardowego złącza RHS. Więc jeśli cdpolecenie w twoim przykładzie nie powiedzie się, nie wyśle ​​on żadnych danych wyjściowych head, ale headw rzeczywistości nadal executeprzeanalizuje nic i nie zwróci żadnych danych wyjściowych.
DopeGhoti
1
bashużywa parsera yacc, a nie jakiejś ad-hoc; jeśli yacc -vprzejdziesz przez to, otrzymasz y.outputprzyjemną gramatykę, która to pokazuje &&i ||łączy listy, a listy składają się w końcu z potoków (a nie odwrotnie); tl; dr; A && B | Cjest taki sam A && { B | C; }, jak oczekiwano. Nie zakładaj kolejności wykonywania między Bi C; polecenia w potoku są uruchamiane równolegle .
mosvy
1
Zauważ, że ta „prawie oficjalna dokumentacja”, na którą wskazujesz, jest całkowicie nieistotna, ponieważ dotyczy operatorów używanych w [...]testach i $((...))ocenach arytmetycznych; w szczególności, ||i &&jak stosować z listy poleceń w języku powłoki mają taki sam priorytet , w przeciwieństwie do odpowiednich podmiotów Club w ocenie arytmetycznej (gdzie &&wiąże mocniej niż ||).
mosvy
@mosvy, ten dokument nie mówi nawet, w jakim kontekście ma zastosowanie tabela, więc wydaje się też mniej niż użyteczny w tym sensie ...
ilkkachu

Odpowiedzi:

21
cd ~/screenshots/ && ls screenshot* | head -n 5

Jest to równoważne z

cd ~/screenshots && { ls screenshot* | head -n 5 ; }

( nawiasy klamrowe grupują polecenia bez podpowłoki ). Pierwszeństwo |jest zatem wyższe (wiąże się mocniej) niż &&i ||. To jest,

A && B | C

i

A || B | C

zawsze oznacza, że tylko wyjście B ma być podane do C . W razie potrzeby możesz użyć (...)lub { ... ; }połączyć polecenia jako jedną całość w celu ujednoznacznienia:

{ A && B ; } | C
A && { B | C ; } # This is the default, but you might sometimes want to be explicit

Możesz to przetestować za pomocą różnych poleceń. Jeśli uciekniesz

echo hello && echo world | tr a-z A-Z

wtedy dostaniesz

hello
WORLD

back: tr a-z A-Zwielkie litery na wejściu i widać, że tylko echo worldzostało do niego wpakowane, podczas gdy echo helloprzeszło samo.


Jest to zdefiniowane w gramatyce powłoki , choć niezbyt wyraźnie: and_orprodukcja (dla &&/ ||) jest zdefiniowana tak, aby mieć aa pipelinew swoim ciele, podczas gdy pipelinezawiera tylko command, co nie zawiera and_or- tylko complete_commandprodukcja może osiągnąć and_ori istnieje tylko w najwyższy poziom i wewnątrz obiektów konstrukcyjnych, takich jak funkcje i pętle.

Możesz ręcznie zastosować tę gramatykę, aby uzyskać parsowanie drzewa dla polecenia, ale Bash sam nic nie zapewnia. Nie znam żadnej powłoki, która wykracza poza to, co jest używane do ich własnego parsowania.

Gramatyka powłoki ma wiele specjalnych przypadków zdefiniowanych tylko półformalnie i poprawienie tej misji może być dość trudne. Nawet sam Bash czasami się mylił , więc praktyczność i ideał mogą być inne.

Istnieją zewnętrzne parsery, które próbują dopasować składnię i utworzyć drzewo, a spośród nich ogólnie polecę Morbiga , który stara się być najbardziej niezawodny.

Michael Homer
źródło
7

TL; DR : separatory list takie jak ;. &, &&i ||zdecyduj o kolejności parsowania.

Podręcznik bash mówi nam:

Listy AND i OR to sekwencje jednego lub więcej potoków oddzielone znakami && i || odpowiednio operatorzy sterujący.

Lub, jak zwięźle to ujęła wiki Bash Hackera

<PIPELINE1> && <PIPELINE2>

Tak więc cd ~/screenshots/ && ls screenshot* | head -n 5 istnieje jeden potok - ls screenshot* | head -n 5i jedno proste polecenie cd ~/screenshots/. Pamiętaj, że zgodnie z instrukcją

Każde polecenie w potoku jest wykonywane jako osobny proces (tj. W podpowłoce).

Z drugiej strony (cd ~/screenshots/ && ls screenshot*) | head -n 5jest inaczej - masz jeden potok: po lewej stronie znajduje się podpowłoka, a po prawej masz head -n 5. W tym przypadku byłoby to przy użyciu notacji OP(A && B) | C


Weźmy inny przykład:

$ echo foo | false &&  echo 123 | tr 2 5
$

Tutaj mamy jedną listę <pipeline1> && <pipeline2>. Ponieważ wiemy, że status wyjścia potoku jest taki sam jak w ostatnim poleceniu i falsezwraca status negatywny, czyli błąd, &&nie zostanie wykonany po prawej stronie.

$ echo foo | true &&  echo 123 | tr 2 5
153

Tutaj lewy potok ma status wyjścia pomyślnego, więc prawy potok jest wykonywany i widzimy jego wynik.


Zauważ, że gramatyka powłoki nie sugeruje faktycznej kolejności wykonywania. Cytując jedną z odpowiedzi Gillesa :

Polecenia potokowe są uruchamiane jednocześnie. Po uruchomieniu ps | grep…, to szczęście losowania (lub kwestia szczegółów działania powłoki w połączeniu z dopracowaniem harmonogramu głęboko w trzewiach jądra), czy ps lub grep zaczyna się najpierw, a w każdym razie kontynuują wykonać jednocześnie.

I z podręcznika bash:

Listy AND i OR są wykonywane z lewą asocjatywnością.

Na tej cd ~/screenshots/ && ls screenshot* | head -n 5podstawie cd ~/screenshots/zostanie wykonane najpierw, ls screenshot* | head -n 5jeśli poprzednie polecenie się powiedzie, ale head -n 5może być pierwszym procesem spawnowanym, a nie lsponieważ są one w przygotowaniu.

Sergiy Kolodyazhnyy
źródło
1
Nie wiem, gdzie pytanie pyta o to, w jakiej kolejności rzeczy się pojawiają.
Michael Homer
„nie ma pierwszeństwa przed pierwszym spawnowaniem cd ~/screenshots/ && ls screenshot*lub head -n 5 Cóż, tak jest w przypadku ((cd ~/screenshots/ && ls screenshot*) | head -n 5). Ale nie mówi nic o niejednoznacznym przypadku, cd ~/screenshots/ && ls screenshot* | head -n 5który wydaje się być sednem pytania (z nawiasami otaczającymi lub bez).
ilkkachu
@ilkkachu Racja, ale dotyczą tego następujące zdania. cd ~/screenshots/ && ls screenshot* musiałby zostać przetworzony pierwszy, ponieważ &&listy są wyższe w kolejności pierwszeństwa (przynajmniej na podstawie odpowiedzi l0b0). Jak myślisz, gdzie powinienem poprawić odpowiedź?
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy, cóż, biorąc pod uwagę, że pytanie ma jedno (cd && ls | head)i drugie ((cd && ls) | head), może być wskazane wyraźne, które masz na myśli.
ilkkachu
@ilkkachu OK, usunę to na razie i
edytuję
3

Oto, gdzie jest określony w bash(1):

SHELL GRAMMAR
[...]
   Pipelines
       A  pipeline  is  a sequence of one or more commands separated by one of
       the control operators | or |&.
[...]
   Lists
       A list is a sequence of one or more pipelines separated by one  of  the
       operators ;, &, &&, or ||, and optionally terminated by one of ;, &, or
       <newline>.

&&Oddziela więc rurociągi.

JoL
źródło
2

Możesz po prostu spróbować echo hello && echo world | less. Zobaczysz, że |ma wyższy priorytet (potok poleceń jest poleceniem). Tam dla twojego drugiego przykładu NIE jest to samo. Ponieważ jednak cdnie ma danych wyjściowych, nie zobaczysz żadnej różnicy, eterycznej.

ctrl-alt-delor
źródło