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.
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.
+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!
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.
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):
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.
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.
Encontrado símbolo echo. Pode ignorar esse símbolo, porque o seguinte comando não precisou ser avaliado.
Encontrado símbolo aaa. Este é um argumento para o comando echo(3), mas como echo(3) não precisou ser avaliado, pode ser ignorado.
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.
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.
Encontrado símbolo bbb. Este é um argumento para o comando echo(6). Como echoprecisava ser avaliado, adicione bbbao buffer de execução.
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.
&&
e||
shell como emcmd1 && cmd2 || cmd3
têm a mesma precedência, o&&
in((...))
e[[...]]
tem precedência sobre||
(((a || b && c))
is((a || (b && c)))
). O mesmo vale para-a
/-o
intest
/[
efind
e&
/|
inexpr
.&&
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.Se você deseja que várias coisas dependam de sua condição, agrupe-as:
Isso não imprime nada, enquanto
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 é:O primeiro comando (
true || echo aaa
) está saindo com0
.fonte
(true || echo aaa) && echo bbb
são exatamente o que estou entendendo.;
no final. Sem isso, não vai funcionar!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!(...)
e{...}
notação: manual do agrupamento de comandoOs 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 ...
No entanto, tentar adicionar vários testes pode gerar resultados inesperados ...
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) ...
Voltando ao nosso último exemplo ...
OK. Se isso estiver correto, por que o próximo ao último exemplo faz eco de alguma coisa?
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) ...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áusulathen
ouelse
.fonte
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):
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.||
. 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.echo
. Pode ignorar esse símbolo, porque o seguinte comando não precisou ser avaliado.aaa
. Este é um argumento para o comandoecho
(3), mas comoecho
(3) não precisou ser avaliado, pode ser ignorado.&&
. 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.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.bbb
. Este é um argumento para o comandoecho
(6). Comoecho
precisava ser avaliado, adicionebbb
ao buffer de execução.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
bbb
eco para o console.fonte