Precedência de Pipe (|) e lógico e (&&) no bash

17

O cenário clássico com Precedência do Operador, você tem uma linha como:

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

E você não sabe se é analisado ((A && B) | C)ou (A && B | C)...

A documentação quase oficial encontrada aqui não lista o canal na lista, portanto não posso simplesmente verificar a tabela.

Além disso, no bash, (não é apenas para alterar a ordem das operações, mas cria um subshell , por isso não tenho 100% de certeza de que essas linhas sejam equivalentes à linha anterior:

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

De maneira mais geral, como conhecer o AST de uma linha de base? Em python, eu tenho uma função que me fornece a árvore, para que eu possa verificar com facilidade a ordem de operação.

Robert Vanden Eynde
fonte
11
Pode ser |útil saber que é apenas um conector que encaixa o stdout do LHS no stdin do RHS. Portanto, se o cdcomando no seu exemplo falhar, ele não enviará nenhuma saída para head, mas headainda assim execute, analisará o nada e não retornará nenhuma saída.
DopeGhoti
11
bashestá usando um analisador yacc e não algo ad-hoc; se você passar por yacc -visso, fornecerá y.outputuma boa gramática que mostra isso &&e ||combina listas, e as listas acabam sendo compostas de pipelines (e não o contrário); tl; dr; A && B | Cé o mesmo que A && { B | C; }, como esperado. Não assuma nenhuma ordem de execução entre Be C; os comandos em um pipeline são executados em paralelo .
mosvy
11
Observe que a "documentação quase oficial" que você aponta é completamente irrelevante, pois trata-se dos operadores usados ​​em [...]testes e $((...))avaliações aritméticas; em particular, ||e &&conforme usado com a lista de comandos na linguagem shell, tem a mesma precedência , diferentemente dos respectivos operadores na Cou na avaliação aritmética (onde &&se vincula mais firmemente que ||).
mosvy
@mosvy, esse documento nem diz em que contexto a tabela se aplica, então parece menos útil nesse sentido também ...
ilkkachu

Respostas:

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

Isso é equivalente a

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

(o grupo de comandos de cintas em conjunto sem uma subcamada ). A precedência de |é, portanto, mais alta (liga-se mais) que &&e ||. Isso é,

A && B | C

e

A || B | C

sempre significa que apenas saída de B é para ser dado a C . Você pode usar (...)ou { ... ; }unir comandos como uma entidade única para desambiguação, se necessário:

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

Você pode testar isso usando alguns comandos diferentes. Se você correr

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

então você terá

hello
WORLD

costas: tr a-z A-Z maiúscula sua entrada , e você pode ver que apenas echo worldfoi canalizada nela, enquanto echo hellofoi executada por conta própria.


Isso é definido na gramática do shell , embora não seja muito claro: a and_orprodução (para &&/ ||) é definida como tendo aa pipelineem seu corpo, enquanto pipelineapenas contémcommand , o que não contém and_or- somente a complete_commandprodução pode alcançar and_ore só existe no nível superior e dentro dos corpos de construções estruturais, como funções e loops.

Você pode aplicar manualmente essa gramática para obter uma árvore de análise para um comando, mas o Bash não fornece nada. Não conheço nenhum shell que faça além do que é usado para sua própria análise.

A gramática do shell tem muitos casos especiais definidos apenas semi-formalmente e pode ser uma missão bastante acertada. Até o próprio Bash às vezes entendeu errado , então os aspectos práticos e o ideal podem ser diferentes.

Existem analisadores externos que tentam corresponder à sintaxe e produzir uma árvore, e dentre esses eu recomendarei Morbig , que tenta ser o mais confiável.

Michael Homer
fonte
7

TL; DR : separadores de lista como ;. &,&& e ||decida a ordem de análise.

O manual do bash nos diz:

As listas AND e OR são sequências de um ou mais pipelines separados pelos && e || operadores de controle, respectivamente.

Ou como o wiki do Bash Hacker coloca sucintamente

<PIPELINE1> && <PIPELINE2>

Portanto, cd ~/screenshots/ && ls screenshot* | head -n 5 existe um pipeline - ls screenshot* | head -n 5e um comando simples cd ~/screenshots/. Observe que de acordo com o manual

Cada comando em um pipeline é executado como um processo separado (isto é, em um subshell).

Por outro lado, (cd ~/screenshots/ && ls screenshot*) | head -n 5é diferente - você tem um pipeline: à esquerda, há subshell e à direita, você tem head -n 5. Nesse caso, usando a notação do OP, seria(A && B) | C


Vamos dar outro exemplo:

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

Aqui temos uma lista <pipeline1> && <pipeline2>. Como sabemos que o status de saída do pipeline é o mesmo do último comando e falseretorna o status negativo, também conhecido como falha, &&não será executado no lado direito.

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

Aqui o pipeline esquerdo tem status de saída bem-sucedida, portanto, o pipeline direito é executado e vemos sua saída.


Observe que a gramática do shell não implica em ordem de execução real. Para citar uma das respostas de Gilles :

Os comandos canalizados são executados simultaneamente. Quando você executa ps | grep…, é a sorte do sorteio (ou uma questão de detalhes do funcionamento do shell combinado com o agendador que faz o ajuste fino nas entranhas do kernel) sobre se o ps ou grep começa primeiro e, em qualquer caso, eles continuam para executar simultaneamente.

E do manual do bash:

As listas AND e OR são executadas com associatividade esquerda.

Com base nisso, cd ~/screenshots/ && ls screenshot* | head -n 5o comando cd ~/screenshots/seria executado primeiro, ls screenshot* | head -n 5se o comando anterior for bem-sucedido, mas head -n 5pode ser o primeiro processo gerado, e não lsporque eles estão em um pipeline.

Sergiy Kolodyazhnyy
fonte
11
Não vejo onde a pergunta faz alguma coisa sobre a ordem em que as coisas são geradas.
Michael Homer
"não há precedência da qual se gera primeiro, cd ~/screenshots/ && ls screenshot*ou head -n 5" Bem, sim, é esse o caso ((cd ~/screenshots/ && ls screenshot*) | head -n 5). Mas não diz nada sobre o caso ambíguo cd ~/screenshots/ && ls screenshot* | head -n 5que parece ser o objetivo da pergunta (com ou sem parênteses).
precisa saber é
@ilkkachu Certo, mas as frases a seguir tocam nisso. cd ~/screenshots/ && ls screenshot* teria que ser processado primeiro porque as &&listas são mais altas na ordem de precedência (com base na resposta de 10b0, pelo menos). Onde você acha que eu deveria melhorar a resposta?
Sergiy Kolodyazhnyy
@SergiyKolodyazhnyy, bem, considerando que a pergunta tem ambos (cd && ls | head)e ((cd && ls) | head), pode ser bom ser explícito sobre qual deles você quer dizer.
precisa saber é
@ilkkachu OK, vou excluir isso por enquanto e editá-lo enquanto isso #
Sergiy Kolodyazhnyy
3

Aqui é onde está especificado em 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>.

Então, &&separa tubulações.

JoL
fonte
2

Você poderia apenas tentar echo hello && echo world | less. Você verá que |tem precedência mais alta (um pipeline de comandos é um comando). Lá para o seu segundo exemplo NÃO é o mesmo. No entanto, como cdnão tem saída, você não verá nenhuma diferença.

ctrl-alt-delor
fonte