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

109

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

true || echo aaa && echo bbb

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

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

    
por Martin Vegter 30.08.2013 / 13:16

4 respostas

108

Em muitas linguagens de computador, os 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 é sem exceção desta regra.

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

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

true || echo aaa

Como true é obviamente verdadeiro, os curtos-circuitos do operador || e toda a instrução são considerados verdadeiros sem a necessidade de avaliar echo aaa como seria de se esperar. Agora resta fazer a operação mais à direita:

(...) && echo bbb

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

true && echo bbb

para que && não cause curto-circuito, e é por isso que você vê bbb ecoado.

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 é somente seguida quando ambos os operadores têm a mesma precedência . Esse não é o caso quando você usa esses operadores em conjunto com palavras-chave como [[...]] ou ((...)) ou usa os operadores -o e -a como argumentos para os comandos test ou [ . Nesses casos, AND ( && ou -a ) tem precedência sobre OR ( || ou -o ). Graças ao comentário de Stephane Chazelas para esclarecer este ponto.
  • Parece que em C e em linguagens semelhantes a C && tem precedência superior a || , que é provavelmente por que você esperava que sua construção original se comportasse como

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

    Este não é o caso de Bash, no entanto, em que ambos os operadores têm a mesma precedência, razão pela qual o Bash analisa sua expressão usando a regra de associatividade à esquerda. Graças ao comentário de Kevin por mencionar isso.

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

por 30.08.2013 / 13:26
54

Se você quiser 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á fazendo. Lembre-se do que o Bash faz com || e && . É tudo sobre o status de retorno do comando anterior. Uma maneira literal de ver 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
    
por 30.08.2013 / 15:27
15

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

Um único teste é direto e não ambíguo ...

[[ 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 ambos são FALSE e TRUE ecoados?

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

Na manpage do bash (editado) ...

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 . Of these list operators, && and ││ have equal precedence, followed by ; and &, which have equal precedence.

A sequence of one or more newlines may appear in a list instead of a semicolon to delimit commands.

If a command is terminated by the control operator &, the shell executes the command in the background in a subshell. The shell does not wait for the command to finish, and the return status is 0. Commands separated by a ; are executed sequentially; the shell waits for each command to terminate in turn. The return status is the exit status of the last command executed.

AND and OR lists are sequences of one of more pipelines separated by the && and ││ control operators, respectively. AND and OR lists are executed with left associativity.

An AND list has the form ...
command1 && command2
Command2 is executed if, and only if, command1 returns an exit status of zero.

An OR list has the form ...
command1 ││ command2
Command2 is executed if and only if command1 returns a non-zero exit status.

The return status of AND and OR lists is the exit status of the last command executed in the list.

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 é correto, então 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

Um único && ou || em uma lista de comandos funciona como esperado, por isso é bastante seguro. Se é 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 operadores && e || , em que cada comando, exceto o último, é um teste (ou seja, dentro de colchetes [[ ]] ), geralmente também são seguros, pois todos, exceto o último, se comportam como esperado. O último operador age mais como uma cláusula then ou else .

    
por 06.09.2013 / 12:35
0

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

  1. Encontrado o símbolo true . Isso precisará ser avaliado quando o final do comando for atingido. Neste ponto, não sei se tem algum argumento. Comando Store no buffer de execução.
  2. Encontrado o símbolo || . O comando anterior está completo, então avalie-o. Comando (buffer) sendo executado: true . Resultado da avaliação: 0 (ou seja, sucesso). Armazena o resultado 0 no registro "última avaliação". Agora considere o símbolo || em si. Isso depende do resultado da última avaliação ser diferente de zero. Registrado "last evaluation" e encontrado 0. Como 0 não é diferente de zero, o seguinte comando não precisa ser avaliado.
  3. Encontrado o símbolo echo . Pode ignorar este símbolo, porque o seguinte comando não precisa ser avaliado.
  4. Encontrado o símbolo aaa . Este é um argumento para comandar echo (3), mas como echo (3) não precisou ser avaliado, ele pode ser ignorado.
  5. Encontrado o símbolo && . Isso depende do resultado da última avaliação ser zero. Checked "last evaluation" register e encontrado 0. Como 0 é zero, o seguinte comando precisa ser avaliado.
  6. Encontrado o símbolo echo . Esse comando precisa ser avaliado quando o final do comando é atingido, porque o comando a seguir precisou ser avaliado. Comando Store no buffer de execução.
  7. Encontrado o símbolo bbb . Este é um argumento para o comando echo (6). Como echo precisou ser avaliado, adicione bbb ao buffer de execução.
  8. Chegou ao fim da linha. O comando anterior agora está completo e precisou ser avaliado. Comando (buffer) sendo executado: echo bbb . Resultado da avaliação: 0 (ou seja, sucesso). Armazena o resultado 0 no registro "última avaliação".

E, claro, a última etapa faz com que bbb seja ecoado para o console.

    
por 25.01.2018 / 21:00

Tags