Diferença entre [[expr1 || expr2]] e [[expr1]] || [[expr2]]

6

Considere duas expressões condicionais expr1e expr2, por exemplo, $i -eq $je $k -eq $l. Podemos escrever isso de bashvárias maneiras. Aqui estão duas possibilidades

[[ expr1 || expr2 ]]

[[ expr1 ]] || [[ expr2 ]]

Tenho certeza de que vi recomendações aqui de que o segundo deveria ser preferido, mas não consigo encontrar evidências para apoiar isso.

Aqui está um exemplo de script que parece demonstrar que não há diferença:

for i in 0 1
do
  for j in 0 1
  do
    for k in 0 1
    do
      for l in 0 1
      do
        if [[ $i -eq $j || $k -eq $l ]]; then printf "1-yes\t"; else printf "1-no\t"; fi
        if [[ $i -eq $j ]] || [[ $k -eq $l ]]; then printf "2-yes\n"; else printf "2-no\n"; fi
      done
    done
  done
done

e saída mostrando que ambas as construções de condição produzem o mesmo resultado:

1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-no    2-no
1-no    2-no
1-yes   2-yes
1-yes   2-yes
1-no    2-no
1-no    2-no
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes
1-yes   2-yes

Existe algum benefício em usar uma construção sobre a outra?

Para pontos de bônus, a mesma pergunta, mas generalizando para várias condições usando ||e &&. Por exemplo [[ expr1 && expr2 || expr3 ]],.

roaima
fonte
No que diz respeito ao motivo pelo qual recomendações desse tipo geralmente são fornecidas. [ou test( não [[ ), procure as OBtags (vinculadas à definição "obsolescente") na especificação POSIX paratest . Em test, existem alguns casos patológicos em que pode ser impossível dizer se (ou )pretende ser sintaxe significativo para o comando de teste ou uma string a ser testados, para que as pessoas que ignoram os marcadores obsolescência e usam essa sintaxe pode realmente precisa do otherwise- "x$foo"prática obsoleta ; com [[, isso não é um problema.
Charles Duffy

Respostas:

7

Eu acho que a recomendação que você viu foi para POSIX sh e / ou o testcomando que funciona como o [comando, em vez da [[construção que apareceu no ksh (obrigado Stéphane Chazelas pela dica) e também é usada, por exemplo, no bash, zsh e outros cartuchos.

Na maioria dos idiomas, como C, quando uma cláusula já é verdadeira ou falsa, não há necessidade de avaliar as partes restantes, dependendo da operação: se verdadeira não após uma lógica ou segui-la, se falsa não após uma lógica e , etc. Isso obviamente permite, por exemplo, parar quando um ponteiro é NULL e não tentar desreferê-lo na próxima cláusula.

Mas o constructo sh[ expr1 -o expr2 ] (incluindo a implementação do bash) não faz isso: ele sempre avalia os dois lados, quando se deseja que apenas expr1 seja avaliado. Isso pode ter sido feito para compatibilidade com a testimplementação do comando. Por outro lado, sh ||e &&siga o princípio usual: não avaliado se não alterar o resultado.

Portanto, a diferença a ser observada seria:

: > /tmp/effect #clear effect
if [ 1 -eq 1 -o $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]; then
    echo true;
fi
cat /tmp/effect

que produz:

true
or-was-evaluated

Acima, cada um [poderia ter sido substituído por /usr/bin/[um alias para o testcomando, usado antes, [foi incorporado aos shells.

Enquanto as duas próximas construções:

: > /tmp/effect #clear effect
if [ 1 -eq 1 ] || [ $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]; then
    echo true;
fi
cat /tmp/effect

ou

: > /tmp/effect #clear effect
if [[ 1 -eq 1 || $(echo 1; echo or-was-evaluated > /tmp/effect) -eq 1 ]]; then
    echo true;
fi
cat /tmp/effect

Apenas renderá truee deixará effectvazio: ||se comporta corretamente e também [[corrigiu esse problema.


ATUALIZAR:

Como o @ StéphaneChazelas comentou, perdi várias diferenças relacionadas à pergunta inicial. Vou colocar aqui o mais importante (pelo menos para mim): prioridade dos operadores.

Embora o shell não considere precedência:

if true || true && false; then
    echo true
else
    echo false
fi

rendimentos (porque não há precedência e, portanto, primeiro true || trueé avaliado e depois && false):

false

dentro [[ ]]do &&operador tem precedência sobre ||:

if [[ 1 -eq 1 || 1 -eq 1 && 1 -eq 0 ]]; then
    echo true
else
    echo false
fi

rendimentos (porque 1 -eq 1 && 1 -eq 0está agrupado e, portanto, é o segundo membro de ||):

true

pelo menos para ksh , bash , zsh .

O mesmo [[ ]]ocorreu com o comportamento aprimorado dos [ ]operadores lógicos diretos e do shell.

AB
fonte
11
[ x -o y ]não precisa avaliar os dois lados. Enquanto o GNU testou o [built-of bashdo, você descobriria strace zsh -c '[ x -o -f /x ]'que [não tenta stat () /x. O mesmo com mksh. Mas é verdade que, -oe -aestão gravemente danificados, não podem ser usados ​​com segurança e estão obsoletos pelo POSIX.
Stéphane Chazelas
11
Uma diferença é que [[...]], por dentro , &&tem maior precedência do que ||, enquanto por fora eles têm igual precedência. Compare ksh -c '[[ x || x && "" ]]'vssh -c 'true || true && false'
Stéphane Chazelas
11
Observe que o padrão [/ testutilitário não possui um ==operador. O operador de igualdade é =(observe que dentro do ksh [[...]], =/ ==não são operadores de igualdade, mas que correspondem a padrões).
Stéphane Chazelas
11
É o contrário. &&tem precedência sobre ||dentro [[...]](ou ((...))), mas não os &&e ||shell operadores. [[ x || y && z ]]is x || (y && z)while x || y && zis (x || y) && z(operadores avaliados da esquerda para a direita sem precedência). Semelhante à forma como *tem precedência sobre +( 1 + 2 * 3é 1 + (2 * 3)), mas +e -têm a mesma precedência.
Stéphane Chazelas
11
Se &&tem mais de precedência ||, então ele deve estar true || (true && false), em vez de(true || true) && false
Prvt_Yadav