Diferencia entre [[expr1 || expr2]] y [[expr1]] || [[expr2]]

6

Considere dos expresiones condicionales expr1y expr2, por ejemplo, $i -eq $jy $k -eq $l. Podemos escribir esto de bashvarias maneras. Aquí hay dos posibilidades.

[[ expr1 || expr2 ]]

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

Estoy bastante seguro de que he visto recomendaciones aquí de que debería preferirse el segundo, pero no puedo encontrar evidencia que lo respalde.

Aquí hay un script de muestra que parece demostrar que no hay diferencia:

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

y salida que muestra que ambas construcciones de condiciones producen el mismo 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

¿Hay algún beneficio en usar una construcción sobre la otra?

Para puntos de bonificación, la misma pregunta pero generalizando a múltiples condiciones usando ||y &&. Por ejemplo, [[ expr1 && expr2 || expr3 ]].

roaima
fuente
Con respecto a por qué las recomendaciones de este tipo a menudo se dan wrt. [o test( no [[ ), busque las OBetiquetas (vinculadas a la definición "obsoleta") en la especificación POSIX paratest . En test, hay algunos casos patológicos donde puede ser imposible decir si (o )está destinado a ser significativo a la sintaxis del comando de prueba o una cadena para ser probados, para que las personas que ignoran los marcadores de obsolescencia y el uso que de sintaxis pueden realmente necesita el contrario- "x$foo"práctica obsoleta ; con [[eso no es un problema.
Charles Duffy el

Respuestas:

7

Creo que la recomendación que viste fue para POSIX sh y / o el testcomando que funciona como el [comando, en lugar de la [[construcción que apareció en ksh (gracias Stéphane Chazelas por la sugerencia) y también se usa, por ejemplo, en bash, zsh y algunos otros conchas

En la mayoría de los lenguajes, como C, cuando ya se sabe que una cláusula es verdadera o falsa, no hay necesidad de evaluar las partes restantes dependiendo de la operación: si es verdadero no después de un lógico o lo sigue, si es falso no después de un lógico y , etc. Esto, por supuesto, permite detenerse cuando un puntero es NULL y no intentar desreferenciarlo en la siguiente cláusula.

Pero la construcción de sh[ expr1 -o expr2 ] (incluida la implementación de bash) no hace esto: siempre evalúa ambos lados, cuando uno quisiera que solo se evalúe expr1 . Esto podría haberse hecho por compatibilidad con la testimplementación del comando. Por otro lado, sh ||y &&sí siguen el principio habitual: no evaluado si no cambiará el resultado.

Entonces la diferencia a tener en cuenta sería:

: > /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

cuyos rendimientos:

true
or-was-evaluated

Arriba, cada uno [podría haber sido reemplazado por /usr/bin/[un alias del testcomando, que se usaba antes de [incorporarse a los shells.

Mientras que las dos siguientes construcciones:

: > /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

o

: > /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

Solo cederá truey dejará effectvacío: se ||comporta correctamente y también [[corrigió este problema.


ACTUALIZAR:

Como comentó @ StéphaneChazelas, perdí varias diferencias relacionadas con la pregunta inicial. Voy a poner aquí el más importante (al menos para mí): prioridad de los operadores.

Si bien el shell no tendrá precedencia:

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

rendimientos (porque no hay precedencia y, por lo tanto, primero true || truese evalúa y luego && false):

false

dentro [[ ]]del &&operador tiene prioridad sobre ||:

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

rendimientos (porque 1 -eq 1 && 1 -eq 0está agrupado y, por lo tanto, es el segundo miembro de ||):

true

al menos para ksh , bash , zsh .

Por lo tanto, [[ ]]ha mejorado el comportamiento sobre ambos [ ]y los operadores lógicos de shell directo.

AB
fuente
1
[ x -o y ]no tiene que evaluar a ambos lados. Si bien GNU testo el [builtin de bashdo, encontrarás strace zsh -c '[ x -o -f /x ]'que eso [no intenta stat () /x. Lo mismo con mksh. Pero es cierto que -oy -aestán severamente rotos, no se pueden usar de manera confiable y son obsoletos por POSIX.
Stéphane Chazelas
1
Una diferencia es que en el interior [[...]], &&tiene mayor precedencia que ||, mientras que fuera de ellos tienen la misma precedencia. Compare ksh -c '[[ x || x && "" ]]'vssh -c 'true || true && false'
Stéphane Chazelas
1
Tenga en cuenta que el estándar [/ testutilidad no tiene un ==operador. El operador de igualdad es =(tenga en cuenta que dentro de ksh [[...]], =/ ==no hay operadores de igualdad, sino patrones que coinciden).
Stéphane Chazelas
1
Es al revés. &&tiene prioridad sobre el ||interior [[...]](o ((...))) pero no los operadores &&y ||shell. [[ x || y && z ]]es x || (y && z)while x || y && zis (x || y) && z(operadores evaluados de izquierda a derecha sin precedencia). Similar a cómo *tiene prioridad sobre +( 1 + 2 * 3es 1 + (2 * 3)) pero +y -tiene la misma prioridad.
Stéphane Chazelas
1
Si &&tiene prioridad sobre ||, entonces debería ser true || (true && false), en lugar de(true || true) && false
Prvt_Yadav