Si la declaración if else es equivalente a lógica y && o || y donde debería preferir uno sobre el otro?


27

Estoy aprendiendo sobre las estructuras de toma de decisiones y me encontré con estos códigos:

if [ -f ./myfile ]
then
     cat ./myfile
else
     cat /home/user/myfile
fi


[ -f ./myfile ] &&
cat ./myfile ||
cat /home/user/myfile

Ambos se comportan igual. ¿Hay alguna ventaja en usar una forma de la otra?



3
No son equivalentes. Vea la excelente respuesta de Icarus. Por ejemplo, considere el caso de que ./myfile existe pero no es legible.
AlexP


Respuestas:


26

No, construcciones if A; then B; else C; fiy noA && B || C son equivalentes .

Con if A; then B; else C; fi, el comando Asiempre se evalúa y ejecuta (al menos se intenta ejecutarlo) y luego se evalúa y ejecuta el comando Bo el comando C.

Con A && B || C, que es la misma para los comandos Ay Bpero diferente para C: comando Cse evalúa y ejecuta si bien A falla o B falla.

En su ejemplo, suponga que chmod u-r ./myfile, entonces, a pesar de tener [ -f ./myfile ]éxito, lo harácat /home/user/myfile

Mi consejo: use A && Bo A || Btodo lo que quiera, esto sigue siendo fácil de leer y comprender y no hay trampa. Pero si te refieres a si ... entonces ... más ... entonces úsalo if A; then B; else C; fi.


29

A la mayoría de las personas les resulta más fácil comprender la if... then... else... fiforma.

Para el a && b || c, debe asegurarse de que bdevuelve verdadero. Esta es una causa de errores sutiles y es una buena razón para evitar este estilo. Si b no devuelve verdadero, estos no son lo mismo.

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

Para pruebas y acciones muy cortas que no tienen una cláusula else, la longitud acortada es atractiva, p. Ej.

 die(){ printf "%s: %s\n" "$0" "$*" >&2 ; exit 1; }

 [ "$#" -eq 2] || die "Needs 2 arguments, input and output"

 if [ "$#" -ne 2 ] ; then
     die "Needs 2 arguments, input and output"
 fi

&&y ||son short circuiting operators, tan pronto como se conozca el resultado de otras pruebas que no sean necesarios se pasan por alto. a && b || cse agrupa como (a && b) || c. Primero ase corre. Si se failsdefine como que no devuelve un estado de salida de 0, entonces el grupo (a && b)es conocido faily bno necesita ejecutarse. El ||no sabe el resultado de la expresión, por lo que debe ejecutarse c. Si atiene éxito (devuelve cero), el &&operador aún no sabe el resultado de a && blo que tiene que correr bpara averiguarlo. Si btiene éxito, entonces a && btiene éxito y ||sabe que el resultado general es exitoso, por lo que no necesita ejecutarse c. Si bfalla entonces||aún no conoce el valor de la expresión, por lo que debe ejecutarse c.


7

El operador && ejecuta el siguiente comando si el comando anterior tuvo una ejecución exitosa (código de salida devuelto ($?) 0 = lógico verdadero).

En el formulario A && B || C, se evalúa el comando (o condición) A y si A devuelve verdadero (éxito, código de salida 0), se ejecuta el comando B. Si A falla (por lo tanto devolverá falso - el código de salida es diferente a 0) y / o B falla (devolver falso ), entonces se ejecutará el comando C.

También el &&operador se utiliza como AND en las comprobaciones de condición y el operador ||funciona como OR en las comprobaciones de condición.

Dependiendo de lo que desee hacer con su script, el formulario A && B || Cpuede usarse para verificaciones de condición como su ejemplo o puede usarse para encadenar comandos y garantizar una serie de comandos que se ejecutarán si los comandos anteriores tuvieron un código de salida 0 exitoso .
Por esta razón, es común ver comandos como:
do_something && do_something_else_that_depended_on_something.

Ejemplos:
apt-get update && apt-get upgrade si la actualización falla, la actualización no se ejecuta (tiene sentido en el mundo real ...).

mkdir test && echo "Something" > test/file
La parte echo "Something"se ejecutará solo si mkdir testfue exitosa y la operación devolvió el código de salida 0 .

./configure --prefix=/usr && make && sudo make install
Generalmente se encuentra en la compilación de trabajos para encadenar los comandos dependientes necesarios.

Si intenta implementar "cadenas" anteriores con if , entonces , de lo contrario , necesitará muchos más comandos y comprobaciones (y, por lo tanto, más código para escribir, más cosas que saldrán mal) para una tarea simple.

Además, tenga en cuenta que los comandos encadenados con && y || son leídos por shell de izquierda a derecha. Es posible que deba agrupar comandos y verificaciones de condición con corchetes para depender del siguiente paso en la salida exitosa de algunos comandos anteriores. Por ejemplo, mira esto:

root@debian:$ true || true && false;echo $?
1 
#read from left to right
#true OR true=true AND false = false = exit code 1=not success

root@debian:$ true || (true && false);echo $?
0 
# true OR (true AND false)=true OR false = true = exit code 0 = success

O un ejemplo de la vida real:

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 ]] && [[ $c -eq 2 ]];echo $?
1 
#condition $a = true OR condition b = true AND condition $c = false
#=> yields false as read from left to right, thus exit code=1 = not ok

root@debian:$ a=1;b=1;c=1;[[ $a -eq 1 ]] || [[ $b -eq 1 && $c -eq 2 ]];echo $?
0 
#vars b and c are checked in a group which returns false, 
#condition check of var a returns true, thus true OR false yields true = exit code 0

Tenga en cuenta que algunos comandos devuelven diferentes códigos de salida según el proceso ejecutado, o devuelven diferentes códigos según sus acciones (por ejemplo, el comando GNU diff, devuelve 1 si dos archivos difieren y 0 si no lo hacen). Dichos comandos deben tratarse con cuidado en && y || .

También solo para tener todo el rompecabezas juntos, tenga en cuenta la concatenación de comandos utilizando el ;operador. Con un formato, A;B;Ctodos los comandos se ejecutarán en serie sin importar cuál fue el código de comando de salida Ay B.


1

Gran parte de la confusión sobre esto puede deberse a la documentación de bash que llama a estas listas AND y OR . Si bien son lógicamente similares al &&y se ||encuentran dentro de los corchetes, funcionan de manera diferente.

Algunos ejemplos pueden ilustrar esto mejor ...

NOTA: Los corchetes simples y dobles ( [ ... ]y [[ ... ]]) son comandos en sí mismos que hacen una comparación y devuelven un código de salida. En realidad no necesitan el if.

cmda  && cmdb  || cmdc

Si cmdasale verdadero, cmdbse ejecuta.
Si cmdasale falso, cmdbNO se ejecuta, pero cmdces.

cmda; cmdb  && cmdc  || cmdd

Cómo cmdase ignora las salidas.
Si cmdbsale verdadero, cmdcse ejecuta.
Si cmdbsale falso, cmdcNO se ejecuta y cmddes.

cmda  && cmdb; cmdc

Si cmdasale verdadero, cmdbse ejecuta, seguido de cmdc.
Si cmdasale falso, cmdbNO se ejecuta pero cmdces.

¿Eh? ¿Por qué se cmdcejecuta?
Porque para el intérprete, un punto y coma ( ;) y una nueva línea significan exactamente lo mismo. Bash ve esa línea de código como ...

cmda  && cmdb
cmdc  

Para lograr lo que se espera, debemos encerrar cmdb; cmdcdentro de llaves para convertirlas en un Compound Command (comando de grupo) . El punto y coma de terminación adicional es solo un requisito de la { ...; }sintaxis. Entonces tenemos ...

cmda && { cmdb; cmdc; }
Si cmdasale verdadero, cmdbse ejecuta, seguido de cmdc.
Si cmdasale falso, ninguno cmdbo cmdcse ejecuta.
La ejecución continúa con la siguiente línea.

Uso

Las listas de comandos condicionales son más útiles para regresar tan pronto como sea posible de las funciones y así evitar interpretar y ejecutar un montón de código innecesario. Sin embargo, los retornos de funciones múltiples significan que uno debe ser obsesivo acerca de mantener las funciones cortas para que sea más fácil asegurarse de que todas las condiciones posibles estén cubiertas.

Aquí hay un ejemplo de un código en ejecución ...

fnInit () {
  :
  _fn="$1"
  ### fnInit "${FUNCNAME}" ...
  ### first argument MUST be name of the calling function
  #
  [[ "$2" == "--help-all" ]]  && { helpAll                      ; return 0; }
  ### pick from list of functions
  #
  [[ "$2" == "--note-all" ]]  && { noteAll                      ; return 0; }
  ### pick from notes in METAFILE
  #
  [[ "$2" == "--version"  ]]  && { versionShow "${_fn}" "${@:3}"; return 0; }
  #
  [[ "$2" == "--function" ]]  && {
    isFnLoaded "$3"           && { "${@:3}"                     ; return 0; }
    #
    errorShow functionnotfound "Unknown function:  $3"
    return 0
  }
  ### call any loaded function
  #
  [[ "$2" == "--help" || "$2" == "-h" ]]  && { noteShow "$_fn" "${@:3}"; return 0; }
  ### fnInit "${FUNCNAME}" --help or -h
  #
  return 1
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.