Precedência dos operadores lógicos do shell &&, ||

126

Estou tentando entender como a precedência do operador lógico funciona no bash. Por exemplo, eu esperava que o comando a seguir não ecoasse nada.

true || echo aaa && echo bbb

No entanto, ao contrário da minha expectativa, bbbé impresso.

Alguém pode explicar, como entender os operadores compostos &&e ||no bash?

Martin Vegter
fonte

Respostas:

127

Em muitas linguagens de computador, operadores com a mesma precedência são associativos à esquerda . Ou seja, na ausência de estruturas de agrupamento, as operações mais à esquerda são executadas primeiro. O Bash não é uma exceção a esta regra.

Isso é importante porque, no Bash, &&e ||tem a mesma precedência.

Então, o que acontece no seu exemplo é que a operação mais à esquerda ( ||) é realizada primeiro:

true || echo aaa

Como trueobviamente é verdade, o ||operador entra em curto-circuito e toda a afirmação é considerada verdadeira sem a necessidade de avaliar echo aaacomo seria de esperar. Agora resta fazer a operação mais à direita:

(...) && echo bbb

Como a primeira operação foi avaliada como verdadeira (ou seja, tinha um status de saída 0), é como se você estivesse executando

true && echo bbb

para que o &&curto-circuito não seja, e é por isso que você vê bbbeco.

Você teria o mesmo comportamento com

false && echo aaa || echo bbb

Notas baseadas nos comentários

  • Você deve observar que a regra de associatividade esquerda é seguida apenas quando os dois operadores têm a mesma precedência. Este não é o caso quando você usa esses operadores em conjunto com palavras-chave como [[...]]ou ((...))ou os operadores -oe -acomo argumentos para os comandos testou [. Nesses casos, AND ( &&ou -a) tem precedência sobre OR ( ||ou -o). Obrigado ao comentário de Stephane Chazelas por esclarecer esse ponto.
  • Parece que nas linguagens C e C &&tem precedência mais alta do ||que provavelmente é por isso que você esperava que sua construção original se comportasse como

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

    Porém, este não é o caso do Bash, no qual os dois operadores têm a mesma precedência, e é por isso que o Bash analisa sua expressão usando a regra de associação à esquerda. Obrigado ao comentário de Kevin por trazer isso à tona.

  • Também pode haver casos em que todas as três expressões são avaliadas. Se o primeiro comando retornar um status de saída diferente de zero, ||ele não entrará em curto-circuito e continuará executando o segundo comando. Se o segundo comando retornar com um status de saída zero, ele &&também não entrará em curto-circuito e o terceiro comando será executado. Obrigado ao comentário de Ignacio Vazquez-Abrams por trazer isso à tona.

Joseph R.
fonte
26
Uma pequena observação para adicionar um pouco mais de confusão: enquanto os operadores &&e ||shell como em cmd1 && cmd2 || cmd3têm a mesma precedência, o &&in ((...))e [[...]]tem precedência sobre ||( ((a || b && c))is ((a || (b && c)))). O mesmo vale para -a/ -oin test/ [e finde &/ |in expr.
Stéphane Chazelas
16
Em linguagens do tipo c, &&tem maior precedência do que ||, portanto, o comportamento esperado do OP aconteceria. Usuários desavisados ​​acostumados a esses idiomas podem não perceber que no bash eles têm a mesma precedência; portanto, vale a pena ressaltar mais explicitamente que eles têm a mesma precedência no bash.
30713 Kevin
Observe também que existem situações em que todos os três comandos podem ser executados, ou seja, se o comando do meio puder retornar verdadeiro ou falso.
Ignacio Vazquez-Abrams
@ Kevin Por favor, verifique a resposta editada.
Joseph R.
1
Hoje, para mim, o principal hit do Google para "precedência do operador do bash" é tldp.org/LDP/abs/html/opprecedence.html ... que afirma que && tem uma precedência maior que ||. Ainda @JosephR. é claramente certo de fato e também de jure. O Dash se comporta da mesma maneira e pesquisando por "precedência" em pubs.opengroup.org/onlinepubs/009695399/utilities/… , vejo que é um requisito POSIX, para que possamos confiar nele. Tudo o que encontrei para relatar bugs foi o endereço de e-mail do autor, que certamente foi spam até a morte nos últimos seis anos. Vou tentar de qualquer maneira ...
Martin Dorey
62

Se você deseja que várias coisas dependam de sua condição, agrupe-as:

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

Isso não imprime nada, enquanto

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

imprime as duas strings.


A razão pela qual isso acontece é muito mais simples do que Joseph está entendendo. Lembre-se do que o Bash faz com ||e &&. É tudo sobre o status de retorno do comando anterior. Uma maneira literal de olhar para o seu comando bruto é:

( true || echo aaa ) && echo bbb

O primeiro comando ( true || echo aaa) está saindo com 0.

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

$ false && echo aaa; echo $?
1
$ false || echo aaa; echo $?
aaa
0
Oli
fonte
7
+1 Para alcançar o resultado pretendido do OP. Observe que os parênteses que você colocou (true || echo aaa) && echo bbbsão exatamente o que estou entendendo.
Joseph R.
1
É bom ver @Oli neste lado do mundo.
Braiam
7
Observe a semi-coluna ;no final. Sem isso, não vai funcionar!
Serge Stroobandt
2
Eu estava tendo problemas com isso porque estava faltando o ponto-e-vírgula após o último comando (por exemplo, echo bbb;nos primeiros exemplos). Depois que eu descobri que estava faltando isso, essa resposta era exatamente o que eu estava procurando. +1 por me ajudar a descobrir como realizar o que eu queria!
Doktor J
5
Vale a pena ler para qualquer um confuso com (...)e {...}notação: manual do agrupamento de comando
ANTARA
16

Os operadores &&e não são substituições in-line exatas para o if-then-else. Embora se usados ​​com cuidado, eles podem realizar praticamente a mesma coisa.||

Um único teste é direto e inequívoco ...

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

No entanto, tentar adicionar vários testes pode gerar resultados inesperados ...

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

Por que os ecos FALSE e TRUE são ecoados?

O que está acontecendo aqui é que não percebemos isso &&e ||somos operadores sobrecarregados que agem de forma diferente dentro dos colchetes de teste condicional [[ ]]do que na lista AND e OR (execução condicional) que temos aqui.

Na página de manual do bash (editada) ...

Listas

Uma lista é uma sequência de um ou mais pipelines separados por um dos operadores;, &, &&, ou ││ e, opcionalmente, finalizados por um de;, &, ou. Desses operadores da lista, && e ││ têm igual precedência, seguidos por; e &, que têm igual precedência.

Uma sequência de uma ou mais novas linhas pode aparecer em uma lista em vez de um ponto-e-vírgula para delimitar comandos.

Se um comando é finalizado pelo operador de controle &, o shell executa o comando em segundo plano em uma subshell. O shell não espera o comando terminar e o status de retorno é 0. Comandos separados por a; são executados sequencialmente; o shell aguarda que cada comando termine por sua vez. O status de retorno é o status de saída do último comando executado.

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

Uma lista AND tem a forma ...
command1 && command2
Command2 é executado se, e somente se, command1 retorna um status de saída igual a zero.

Uma lista OR tem a forma ...
command1 ││ command2
Command2 é executado se, e somente se, command1 retornar um status de saída diferente de zero.

O status de retorno das listas AND e OR é o status de saída do último comando executado na lista.

Voltando ao nosso último exemplo ...

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

OK. Se isso estiver correto, por que o próximo ao último exemplo faz eco de alguma coisa?

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

O risco de erros lógicos ao usar mais de um &&ou ||em uma lista de comandos é bastante alto.

Recomendações

Uma única lista &&ou ||uma lista de comandos funciona conforme o esperado, portanto, é bastante seguro. Se for uma situação em que você não precisa de uma cláusula else, algo como o seguinte pode ser mais claro a seguir (as chaves são necessárias para agrupar os dois últimos comandos) ...

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

Múltiplos &&e ||operadores, onde cada comando, exceto o último, é um teste (ou seja, entre colchetes [[ ]]), geralmente também são seguros, pois todos, exceto o último operador, se comportam conforme o esperado. O último operador atua mais como uma cláusula thenou else.

DocSalvager
fonte
0

Também fiquei confuso com isso, mas aqui está como penso sobre a maneira como o Bash lê sua declaração (como lê os símbolos da esquerda para a direita):

  1. Encontrado símbolo true. Isso precisará ser avaliado assim que o final do comando for alcançado. Neste ponto, não sei se há algum argumento. Armazenar comando no buffer de execução.
  2. Encontrado símbolo ||. O comando anterior agora está completo, portanto, avalie-o. Comando (buffer) ser executado: true. Resultado da avaliação: 0 (ou seja, sucesso). Armazene o resultado 0 no registro "última avaliação". Agora considere o ||próprio símbolo . Isso depende do resultado da última avaliação ser diferente de zero. O registro "last assessment" marcado e encontrado 0. Como 0 não é diferente de zero, o seguinte comando não precisa ser avaliado.
  3. Encontrado símbolo echo. Pode ignorar esse símbolo, porque o seguinte comando não precisou ser avaliado.
  4. Encontrado símbolo aaa. Este é um argumento para o comando echo(3), mas como echo(3) não precisou ser avaliado, pode ser ignorado.
  5. Encontrado símbolo &&. Isso depende do resultado da última avaliação ser zero. O registro "last assessment" verificado e encontrado 0. Como 0 é zero, o seguinte comando precisa ser avaliado.
  6. Encontrado símbolo echo. Este comando precisa ser avaliado assim que o final do comando for alcançado, porque o comando a seguir precisava ser avaliado. Armazenar comando no buffer de execução.
  7. Encontrado símbolo bbb. Este é um argumento para o comando echo(6). Como echoprecisava ser avaliado, adicione bbbao buffer de execução.
  8. Atingiu o fim da linha. O comando anterior agora está completo e precisava ser avaliado. Comando (buffer) ser executado: echo bbb. Resultado da avaliação: 0 (ou seja, sucesso). Armazene o resultado 0 no registro "última avaliação".

E, é claro, o último passo faz bbbeco para o console.

Kidburla
fonte