¿Cómo salir si falla un comando?

222

Soy un novato en scripting de shell. Quiero imprimir un mensaje y salir de mi script si falla un comando. He intentado:

my_command && (echo 'my_command failed; exit)

Pero no funciona. Sigue ejecutando las instrucciones siguiendo esta línea en el script. Estoy usando Ubuntu y bash.

usuario459246
fuente
55
¿Pretendía que la cita no cerrada fuera un error de sintaxis que provocaría una falla / salida? si no, debe cerrar la cita en su ejemplo.
placas

Respuestas:

406

Tratar:

my_command || { echo 'my_command failed' ; exit 1; }

Cuatro cambios:

  • Cambiar &&a||
  • Usar { }en lugar de( )
  • Introducir ;después exity
  • espacios después {y antes}

Como desea imprimir el mensaje y salir solo cuando el comando falla (sale con un valor distinto de cero), no necesita ||un &&.

cmd1 && cmd2

se ejecutará cmd2cuando cmd1tenga éxito (valor de salida 0). Donde como

cmd1 || cmd2

se ejecutará cmd2cuando cmd1falle (valor de salida distinto de cero).

El uso ( )hace que el comando dentro de ellos se ejecute en un sub-shell y llamar a exitdesde allí hace que salga del sub-shell y no de su shell original, por lo tanto, la ejecución continúa en su shell original.

Para superar este uso { }

Los dos últimos cambios son requeridos por bash.

codictorio
fuente
44
Parece estar "invertido". Si una función "tiene éxito", devuelve 0 y si "falla" devuelve un valor distinto de cero, por lo tanto, se puede esperar que && evalúe cuándo la primera mitad devuelve un valor distinto de cero. Eso no significa que la respuesta anterior sea incorrecta, no, es correcta. && y || en scripts, el trabajo se basa en el éxito, no en el valor de retorno.
CashCow
77
Parece invertido, pero léalo y tiene sentido: "
ejecute
2
La lógica detrás de esto es que el lenguaje utiliza la evaluación de cortocircuito (SCE). Con SCE, f la expresión es de la forma "p OR q", y se evalúa que p es verdadera, entonces no hay razón para mirar siquiera q. Si la expresión tiene la forma "p Y q", y se evalúa que p es falsa, no hay razón para mirar q. La razón de esto es doble: 1) es más rápido, por razones obvias y 2) evita ciertos tipos de errores (por ejemplo: "si x! = 0 Y 10 / x> 5" se bloqueará si no hay SCE ) La capacidad de usarlo en la línea de comando como esta es un efecto secundario feliz.
user2635263
2
Recomendaría hacer eco a STDERR:{ (>&2 echo 'my_command failed') ; exit 1; }
rynop
127

Las otras respuestas han cubierto bien la pregunta directa, pero también puede interesarle usar set -e. Con eso, cualquier comando que falle (fuera de contextos específicos como las ifpruebas) hará que el script se cancele. Para ciertos scripts, es muy útil.

Daenyth
fuente
3
Desafortunadamente, también es muy peligroso: set -etiene un conjunto de reglas largo y arcano sobre cuándo funciona y cuándo no. (Si alguien corre if yourfunction; then ..., set -enunca disparará adentro yourfunctiono cualquier cosa que llame, porque ese código se considera "marcado"). Consulte la sección de ejercicios de BashFAQ # 105 , donde se analizan esta y otras dificultades (algunas de las cuales solo se aplican a versiones de shell específicas, por lo que debe asegurarse de probar cada versión que pueda usarse para ejecutar su script).
Charles Duffy el
67

Tenga en cuenta también que el estado de salida de cada comando se almacena en la variable de shell $ ?, que puede verificar inmediatamente después de ejecutar el comando. Un estado distinto de cero indica falla:

my_command
if [ $? -eq 0 ]
then
    echo "it worked"
else
    echo "it failed"
fi
Alex Howansky
fuente
10
Esto solo puede ser reemplazado por my_command, no hay necesidad de usar el comando de prueba aquí.
Bart Sas
55
+1 porque creo que no debería ser castigado por enumerar esta alternativa, debería estar sobre la mesa, aunque es un poco feo y, en mi experiencia, es demasiado fácil ejecutar otro comando en el medio sin notar la naturaleza de la prueba (tal vez yo ' soy estúpido)
Tony Delroy
1
@BartSas Si el comando es largo, es mejor ponerlo en su propia línea. Hace que el script sea más legible.
Michael
@Michael, no si crea dependencias entre líneas, donde necesita saber qué sucedió en la línea A antes de poder entender la línea B (o, peor aún, necesita saber qué sucederá en la línea B antes de saber que es seguro agregar algo nuevo después del final de la línea A). He visto demasiadas veces que alguien agregó un echo "Just finished step A", sin saber que eso echosobrescribió el $?que iba a verificarse más tarde.
Charles Duffy
67

Si desea ese comportamiento para todos los comandos en su script, simplemente agregue

  set -e 
  set -o pipefail

Al comienzo del guión. Este par de opciones le dice al intérprete bash que salga cuando un comando regrese con un código de salida distinto de cero.

Sin embargo, esto no le permite imprimir un mensaje de salida.

damienfrancois
fuente
8
Puede ejecutar comandos al salir utilizando el trapcomando incorporado bash.
Gavin Smith
Esta es la respuesta a la pregunta XY.
jwg
55
Puede ser interesante explicar qué comandos hacen de forma independiente en lugar del par. Especialmente porque set -o pipefailpodría no ser el comportamiento deseado. Aún así, gracias por señalarlo!
Antoine Pinsard el
3
set -eno es del todo confiable, consistente o predecible! Para convencerse de eso, revise la sección de ejercicios de BashFAQ # 105 , o la tabla que compara los comportamientos de diferentes shells en in-ulm.de/~mascheck/various/set-e
Charles Duffy
14

He pirateado el siguiente idioma:

echo "Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Done."

Preceda cada comando con un eco informativo y siga cada comando con esa misma
if [ $? -ne 0 ];...línea. (Por supuesto, puede editar ese mensaje de error si lo desea).

Grant Birchmeier
fuente
Esto podría definirse como una función, por lo tanto, reutilícela.
Eric Wang
1
'si [$? -ne 0]; entonces ... fi 'es una forma complicada de escribir' || '
Kevin Cline
if ! javac *.java; then ...- ¿Por qué molestarse $?en absoluto?
Charles Duffy el
Charles - porque soy un guionista fiesta mediocre en el mejor :)
Subvención Birchmeier
10

Siempre que my_commandesté diseñado canónicamente, es decir , devuelve 0 cuando tiene éxito, entonces &&es exactamente lo contrario de lo que desea. Que desea ||.

También tenga en cuenta que (no me parece correcto en bash, pero no puedo intentar desde donde estoy. Dime.

my_command || {
    echo 'my_command failed' ;
    exit 1; 
}
Benoit
fuente
IIRC, necesita punto y coma después de cada una de las líneas dentro de {}
Alex Howansky
99
@ Alex: no si están en una línea separada.
Pausado hasta nuevo aviso.
5

También puede usar, si desea conservar el estado de error de salida, y tener un archivo legible con un comando por línea:

my_command1 || exit $?
my_command2 || exit $?

Esto, sin embargo, no imprimirá ningún mensaje de error adicional. Pero en algunos casos, el error será impreso por el comando fallido de todos modos.

alexpirine
fuente
1
No hay razón para que sea así exit $?; solo se exitusa $?como su valor predeterminado.
Charles Duffy
@CharlesDuffy no lo sabía. Impresionante, ¡así que es aún más simple!
alexpirine
3

El trapshell incorporado permite la captura de señales y otras condiciones útiles, incluida la ejecución fallida de comandos (es decir, un estado de retorno distinto de cero). Entonces, si no desea probar explícitamente el estado de retorno de cada comando, puede decir trap "your shell code" ERRy el código de shell se ejecutará cada vez que un comando devuelva un estado que no sea cero. Por ejemplo:

trap "echo script failed; exit 1" ERR

Tenga en cuenta que, al igual que con otros casos de captura de comandos fallidos, las tuberías necesitan un tratamiento especial; lo anterior no atrapará false | true.

kozel
fuente