Precedencia de los operadores lógicos de shell &&, ||
126
Estoy tratando de entender cómo funciona la precedencia del operador lógico en bash. Por ejemplo, hubiera esperado que el siguiente comando no haga eco de nada.
true || echo aaa && echo bbb
Sin embargo, contrario a lo que esperaba, bbbse imprime.
¿Alguien puede explicar, cómo puedo dar sentido a los compuestos &&y los ||operadores en bash?
En muchos lenguajes de computadora, los operadores con la misma precedencia son asociativos a la izquierda . Es decir, en ausencia de estructuras de agrupación, las operaciones más a la izquierda se ejecutan primero. Bash no es una excepción a esta regla.
Esto es importante porque, en Bash, &&y ||tienen la misma precedencia.
Entonces, lo que sucede en su ejemplo es que la operación más a la izquierda ( ||) se realiza primero:
true || echo aaa
Como truees obviamente cierto, el ||operador hace un cortocircuito y toda la declaración se considera verdadera sin la necesidad de evaluar echo aaacomo era de esperar. Ahora queda por hacer la operación más correcta:
(...)&& echo bbb
Como la primera operación se evaluó como verdadera (es decir, tenía un estado de salida 0), es como si estuviera ejecutando
true && echo bbb
así &&que no se cortocircuitará, razón por la cual ves bbbeco.
Obtendría el mismo comportamiento con
false && echo aaa || echo bbb
Notas basadas en los comentarios.
Debe tener en cuenta que la regla de asociatividad izquierda solo se sigue cuando ambos operadores tienen la misma precedencia. Este no es el caso cuando usa estos operadores junto con palabras clave como [[...]]o ((...))o usa los operadores -oy -acomo argumentos para los comandos testo [. En tales casos, AND ( &&o -a) tiene prioridad sobre OR ( ||o -o). Gracias al comentario de Stephane Chazelas por aclarar este punto.
Parece que en los lenguajes C y C &&tiene mayor prioridad que la ||que probablemente sea la razón por la que esperaba que su construcción original se comportara como
true ||(echo aaa && echo bbb).
Sin embargo, este no es el caso con Bash, en el que ambos operadores tienen la misma precedencia, por lo que Bash analiza su expresión utilizando la regla de asociatividad izquierda. Gracias al comentario de Kevin por mencionar esto.
También puede haber casos en los que se evalúan las 3 expresiones. Si el primer comando devuelve un estado de salida distinto de cero, ||no se cortocircuitará y ejecutará el segundo comando. Si el segundo comando regresa con un estado de salida cero, entonces &&tampoco se cortocircuitará y se ejecutará el tercer comando. Gracias al comentario de Ignacio Vázquez-Abrams por mencionar esto.
Una pequeña nota para agregar un poco más de confusión: mientras que los operadores &&y ||shell como en cmd1 && cmd2 || cmd3tienen la misma precedencia, los &&in ((...))y [[...]]tienen precedencia sobre ||( ((a || b && c))is ((a || (b && c)))). Lo mismo va para -a/ -oen test/ [y finde &/ |en expr.
Stéphane Chazelas
16
En lenguajes tipo c, &&tiene mayor prioridad que ||, por lo que el comportamiento esperado del OP ocurriría. Los usuarios incautos acostumbrados a tales lenguajes pueden no darse cuenta de que en bash tienen la misma precedencia, por lo que vale la pena señalar más explícitamente que sí tienen la misma precedencia en bash.
Kevin
También tenga en cuenta que hay situaciones en las que se pueden ejecutar los tres comandos, es decir, si el comando del medio puede devolver verdadero o falso.
Ignacio Vazquez-Abrams
@ Kevin Por favor revise la respuesta editada.
Joseph R.
1
Hoy, para mí, el principal éxito de Google para "precedencia de operador bash" es tldp.org/LDP/abs/html/opprecedence.html ... que afirma que && tiene una precedencia más alta que ||. Sin embargo, @JosephR. es claramente correcto de facto y de jure también. Dash se comporta igual y buscando "precedencia" en pubs.opengroup.org/onlinepubs/009695399/utilities/… , veo que es un requisito POSIX, por lo que podemos confiar en él. Todo lo que encontré para la notificación de errores fue la dirección de correo electrónico del autor, que seguramente ha sido enviada por correo no deseado en los últimos seis años. Lo intentaré de todos modos ...
Martin Dorey
62
Si desea que varias cosas dependan de su condición, agrúpelas:
true ||{ echo aaa && echo bbb;}
Eso no imprime nada, mientras
true &&{ echo aaa && echo bbb;}
imprime ambas cadenas.
La razón por la que esto sucede es mucho más simple de lo que Joseph está haciendo. Recuerda lo que Bash hace con ||y &&. Se trata del estado de retorno del comando anterior. Una forma literal de ver su comando sin procesar es:
( true || echo aaa )&& echo bbb
El primer comando ( true || echo aaa) está saliendo con 0.
+1 Para lograr el resultado previsto del OP. Tenga en cuenta que los paréntesis que colocó (true || echo aaa) && echo bbbes exactamente lo que estoy haciendo.
Joseph R.
1
Es bueno ver a @Oli en este lado del mundo.
Braiam
77
Tenga en cuenta la semi-columna ;al final. ¡Sin esto, no funcionará!
Serge Stroobandt
2
Estaba teniendo problemas con esto porque me faltaba el punto y coma final después del último comando (por ejemplo, echo bbb;en los primeros ejemplos). Una vez que descubrí que me estaba perdiendo eso, esta respuesta fue exactamente lo que estaba buscando. ¡+1 por ayudarme a descubrir cómo lograr lo que quería!
Los operadores &&y no|| son reemplazos en línea exactos para if-then-else. Aunque si se usan con cuidado, pueden lograr lo mismo.
Una sola prueba es sencilla e inequívoca ...
[[ A == A ]]&& echo TRUE # TRUE[[ A == B ]]&& echo TRUE # [[ A == A ]]|| echo FALSE # [[ A == B ]]|| echo FALSE # FALSE
Sin embargo, intentar agregar múltiples pruebas puede producir 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 qué se hacen eco FALSO y VERDADERO?
Lo que sucede aquí es que no nos hemos dado cuenta de eso &&y ||somos operadores sobrecargados que actúan de manera diferente dentro de los corchetes de prueba condicionales [[ ]]que en la lista AND y OR (ejecución condicional) que tenemos aquí.
Desde la página de manual de bash (editado) ...
Liza
Una lista es una secuencia de una o más tuberías separadas por uno de los operadores;, &, &&, o ││, y opcionalmente terminadas por uno de;, &, o. De estos operadores de lista, && y ││ tienen la misma precedencia, seguidos de; y &, que tienen igual precedencia.
Puede aparecer una secuencia de una o más líneas nuevas en una lista en lugar de un punto y coma para delimitar los comandos.
Si el operador de control termina un comando &, el shell ejecuta el comando en segundo plano en un subshell. El shell no espera a que termine el comando y el estado de retorno es 0. Comandos separados por a; se ejecutan secuencialmente; el shell espera que cada comando termine a su vez. El estado de retorno es el estado de salida del último comando ejecutado.
Las listas AND y OR son secuencias de uno o más canales separados por los operadores de control && y ││, respectivamente. Las listas AND y OR se ejecutan con asociatividad izquierda.
Una lista AND tiene la forma ... command1 && command2
Command2 se ejecuta si, y solo si, command1 devuelve un estado de salida de cero.
Una lista OR tiene la forma ... command1 ││ command2
Command2 se ejecuta si y solo si command1 devuelve un estado de salida distinto de cero.
El estado de retorno de las listas AND y OR es el estado de salida del último comando ejecutado en la lista.
Volviendo a nuestro último ejemplo ...
[[ 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"
Bueno. Si eso es correcto, ¿por qué el penúltimo ejemplo hace eco de algo?
[[ 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"
El riesgo de errores lógicos cuando se usa más de uno &&o ||en una lista de comandos es bastante alto.
Recomendaciones
Un solo &&o ||en una lista de comandos funciona como se esperaba, por lo que es bastante seguro. Si es una situación en la que no necesita una cláusula else, puede ser más claro seguir algo como lo siguiente (las llaves se requieren para agrupar los últimos 2 comandos) ...
[[ $1 ==--help ]]&&{ echo "$HELP"; exit;}
Los operadores múltiples &&y ||, donde cada comando, excepto el último, es una prueba (es decir, entre corchetes [[ ]]), también suelen ser seguros, ya que todos menos el último operador se comportan como se esperaba. El último operador actúa más como una theno elsecláusula.
También me confundí con esto, pero así es como pienso en la forma en que Bash lee su declaración (ya que lee los símbolos de izquierda a derecha):
Símbolo que se encuentra true. Esto deberá evaluarse una vez que se llegue al final del comando. En este punto, no sé si tiene algún argumento. Almacene el comando en el búfer de ejecución.
Símbolo que se encuentra ||. El comando anterior ahora está completo, así que evalúelo. Comando (buffer) está ejecutando: true. Resultado de la evaluación: 0 (es decir, éxito). Almacene el resultado 0 en el registro de "última evaluación". Ahora considere el símbolo ||mismo. Esto depende del resultado de que la última evaluación sea distinta de cero. Se verificó el registro de "última evaluación" y se encontró 0. Dado que 0 no es distinto de cero, no es necesario evaluar el siguiente comando.
Símbolo que se encuentra echo. Puede ignorar este símbolo, porque el siguiente comando no necesitaba ser evaluado.
Símbolo que se encuentra aaa. Este es un argumento para el comando echo(3), pero como echo(3) no necesitaba ser evaluado, puede ignorarse.
Símbolo que se encuentra &&. Esto depende de que el resultado de la última evaluación sea cero. Se verificó el registro de "última evaluación" y se encontró 0. Ya que 0 es cero, el siguiente comando necesita ser evaluado.
Símbolo que se encuentra echo. Este comando debe evaluarse una vez que se alcanza el final del comando, porque el siguiente comando sí tuvo que evaluarse. Almacene el comando en el búfer de ejecución.
Símbolo que se encuentra bbb. Este es un argumento para ordenar echo(6). Como echosí necesitaba ser evaluado, agregue bbbal búfer de ejecución.
Alcanzado el final de la línea. El comando anterior ahora está completo y necesitaba ser evaluado. Comando (buffer) está ejecutando: echo bbb. Resultado de la evaluación: 0 (es decir, éxito). Almacene el resultado 0 en el registro de "última evaluación".
Y, por supuesto, el último paso hace bbbque se repita en la consola.
&&
y||
shell como encmd1 && cmd2 || cmd3
tienen la misma precedencia, los&&
in((...))
y[[...]]
tienen precedencia sobre||
(((a || b && c))
is((a || (b && c)))
). Lo mismo va para-a
/-o
entest
/[
yfind
e&
/|
enexpr
.&&
tiene mayor prioridad que||
, por lo que el comportamiento esperado del OP ocurriría. Los usuarios incautos acostumbrados a tales lenguajes pueden no darse cuenta de que en bash tienen la misma precedencia, por lo que vale la pena señalar más explícitamente que sí tienen la misma precedencia en bash.Si desea que varias cosas dependan de su condición, agrúpelas:
Eso no imprime nada, mientras
imprime ambas cadenas.
La razón por la que esto sucede es mucho más simple de lo que Joseph está haciendo. Recuerda lo que Bash hace con
||
y&&
. Se trata del estado de retorno del comando anterior. Una forma literal de ver su comando sin procesar es:El primer comando (
true || echo aaa
) está saliendo con0
.fuente
(true || echo aaa) && echo bbb
es exactamente lo que estoy haciendo.;
al final. ¡Sin esto, no funcionará!echo bbb;
en los primeros ejemplos). Una vez que descubrí que me estaba perdiendo eso, esta respuesta fue exactamente lo que estaba buscando. ¡+1 por ayudarme a descubrir cómo lograr lo que quería!(...)
y con{...}
notación: manual de agrupación de comandosLos operadores
&&
y no||
son reemplazos en línea exactos para if-then-else. Aunque si se usan con cuidado, pueden lograr lo mismo.Una sola prueba es sencilla e inequívoca ...
Sin embargo, intentar agregar múltiples pruebas puede producir resultados inesperados ...
¿Por qué se hacen eco FALSO y VERDADERO?
Lo que sucede aquí es que no nos hemos dado cuenta de eso
&&
y||
somos operadores sobrecargados que actúan de manera diferente dentro de los corchetes de prueba condicionales[[ ]]
que en la lista AND y OR (ejecución condicional) que tenemos aquí.Desde la página de manual de bash (editado) ...
Volviendo a nuestro último ejemplo ...
Bueno. Si eso es correcto, ¿por qué el penúltimo ejemplo hace eco de algo?
El riesgo de errores lógicos cuando se usa más de uno
&&
o||
en una lista de comandos es bastante alto.Recomendaciones
Un solo
&&
o||
en una lista de comandos funciona como se esperaba, por lo que es bastante seguro. Si es una situación en la que no necesita una cláusula else, puede ser más claro seguir algo como lo siguiente (las llaves se requieren para agrupar los últimos 2 comandos) ...Los operadores múltiples
&&
y||
, donde cada comando, excepto el último, es una prueba (es decir, entre corchetes[[ ]]
), también suelen ser seguros, ya que todos menos el último operador se comportan como se esperaba. El último operador actúa más como unathen
oelse
cláusula.fuente
También me confundí con esto, pero así es como pienso en la forma en que Bash lee su declaración (ya que lee los símbolos de izquierda a derecha):
true
. Esto deberá evaluarse una vez que se llegue al final del comando. En este punto, no sé si tiene algún argumento. Almacene el comando en el búfer de ejecución.||
. El comando anterior ahora está completo, así que evalúelo. Comando (buffer) está ejecutando:true
. Resultado de la evaluación: 0 (es decir, éxito). Almacene el resultado 0 en el registro de "última evaluación". Ahora considere el símbolo||
mismo. Esto depende del resultado de que la última evaluación sea distinta de cero. Se verificó el registro de "última evaluación" y se encontró 0. Dado que 0 no es distinto de cero, no es necesario evaluar el siguiente comando.echo
. Puede ignorar este símbolo, porque el siguiente comando no necesitaba ser evaluado.aaa
. Este es un argumento para el comandoecho
(3), pero comoecho
(3) no necesitaba ser evaluado, puede ignorarse.&&
. Esto depende de que el resultado de la última evaluación sea cero. Se verificó el registro de "última evaluación" y se encontró 0. Ya que 0 es cero, el siguiente comando necesita ser evaluado.echo
. Este comando debe evaluarse una vez que se alcanza el final del comando, porque el siguiente comando sí tuvo que evaluarse. Almacene el comando en el búfer de ejecución.bbb
. Este es un argumento para ordenarecho
(6). Comoecho
sí necesitaba ser evaluado, agreguebbb
al búfer de ejecución.echo bbb
. Resultado de la evaluación: 0 (es decir, éxito). Almacene el resultado 0 en el registro de "última evaluación".Y, por supuesto, el último paso hace
bbb
que se repita en la consola.fuente