¿Anular un script de shell si algún comando devuelve un valor distinto de cero?

437

Tengo un script de shell Bash que invoca una serie de comandos. Me gustaría que el script de shell salga automáticamente con un valor de retorno de 1 si alguno de los comandos devuelve un valor distinto de cero.

¿Es esto posible sin verificar explícitamente el resultado de cada comando?

p.ej

dosomething1
if [[ $? -ne 0 ]]; then
    exit 1
fi

dosomething2
if [[ $? -ne 0 ]]; then
    exit 1
fi
Jin Kim
fuente
8
Además de set -e, también hacer set -u(o set -eu). -upone fin al comportamiento idiota y de ocultación de errores al que puede acceder a cualquier variable inexistente y obtener un valor en blanco sin diagnóstico.
Kaz

Respuestas:

742

Agregue esto al comienzo del script:

set -e

Esto hará que el shell salga de inmediato si sale un comando simple con un valor de salida distinto de cero. Un comando simple es cualquier comando que no forme parte de una prueba if, while o while, o parte de un && o || lista.

Consulte la página del comando man bash (1) en el comando interno "set" para obtener más detalles.

Personalmente, comienzo casi todos los scripts de shell con "set -e". Es realmente molesto que un guión continúe obstinadamente cuando algo falla en el medio y rompe las suposiciones para el resto del guión.

Ville Laurikari
fuente
36
Eso funcionaría, pero me gusta usar "#! / Usr / bin / env bash" porque frecuentemente ejecuto bash desde otro lugar que no sea / bin. Y "#! / Usr / bin / env bash -e" no funciona. Además, es bueno tener un lugar para modificar para leer "set -xe" cuando quiero activar el seguimiento para la depuración.
Ville Laurikari
48
Además, las banderas en la línea shebang se ignoran si un script se ejecuta como bash script.sh.
Tom Anderson el
27
Solo una nota: si declara funciones dentro del script bash, las funciones deberán haber establecido -e redeclarado dentro del cuerpo de la función si desea ampliar esta funcionalidad.
Jin Kim
8
Además, si obtiene su script, la línea shebang será irrelevante.
44
@ JinKim Ese no parece ser el caso en bash 3.2.48. Intente lo siguiente dentro de un script: set -e; tf() { false; }; tf; echo 'still here'. Incluso sin set -edentro del cuerpo de tf(), la ejecución se aborta. Quizás quisiste decir que las subcapasset -e no heredan , lo cual es cierto.
mklement0
202

Para agregar a la respuesta aceptada:

Tenga en cuenta que a set -eveces no es suficiente, especialmente si tiene tuberías.

Por ejemplo, suponga que tiene este script

#!/bin/bash
set -e 
./configure  > configure.log
make

... que funciona como se esperaba: un error en configureaborta la ejecución.

Mañana harás un cambio aparentemente trivial:

#!/bin/bash
set -e 
./configure  | tee configure.log
make

... y ahora no funciona. Esto se explica aquí , y se proporciona una solución (solo Bash):

#! / bin / bash
conjunto -e 
set -o pipefail

./configure | tee configure.log
hacer
leonbloy
fuente
1
¡Gracias por explicar la importancia de tener pipefailque aceptar set -o!
Malcolm
83

Las declaraciones if en su ejemplo son innecesarias. Solo hazlo así:

dosomething1 || exit 1

Si toma el consejo de Ville Laurikari y lo usa set -epara algunos comandos, es posible que deba usar esto:

dosomething || true

Esto || truehará que la canalización de comandos tenga un truevalor de retorno, incluso si el comando falla, por lo que la -eopción no matará el script.

Zan Lynx
fuente
1
Me gusta esto. Especialmente porque la respuesta principal está centrada en bash (no tengo nada claro si / en qué medida se aplica a las secuencias de comandos zsh). Y podría buscarlo, pero tu es más claro, porque la lógica.
g33kz0r
set -eno está centrado en bash, es compatible incluso en el Bourne Shell original.
Marcos Vives Del Sol
27

Si tiene que realizar una limpieza al salir, también puede usar 'trap' con la pseudo-señal ERR. Esto funciona de la misma manera que atrapar INT o cualquier otra señal; bash lanza ERR si algún comando sale con un valor distinto de cero:

# Create the trap with   
#    trap COMMAND SIGNAME [SIGNAME2 SIGNAME3...]
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM
command1
command2
command3
# Partially turn off the trap.
trap - ERR
# Now a control-C will still cause cleanup, but
# a nonzero exit code won't:
ps aux | grep blahblahblah

O, especialmente si está usando "set -e", podría atrapar EXIT; su trampa se ejecutará cuando el script salga por cualquier motivo, incluido un final normal, interrupciones, una salida causada por la opción -e, etc.

fholo
fuente
12

La $?variable rara vez se necesita. El pseudo-idioma command; if [ $? -eq 0 ]; then X; fisiempre debe escribirse como if command; then X; fi.

Los casos en los que $?se requiere es cuando se debe verificar con varios valores:

command
case $? in
  (0) X;;
  (1) Y;;
  (2) Z;;
esac

o cuando $?necesita ser reutilizado o manipulado de otra manera:

if command; then
  echo "command successful" >&2
else
  ret=$?
  echo "command failed with exit code $ret" >&2
  exit $ret
fi
Mark Edgar
fuente
3
¿Por qué "siempre debe escribirse como"? Quiero decir, ¿por qué " debería " ser así? Cuando un comando es largo (piense en invocar GCC con una docena de opciones), entonces es mucho más legible ejecutar el comando antes de verificar el estado de retorno.
ysap
Si un comando es demasiado largo, puede dividirlo nombrándolo (definir una función de shell).
Mark Edgar
12

Ejecútelo con -eo set -een la parte superior.

También mira set -u.

lumpynose
fuente
34
Para salvar potencialmente a otros la necesidad de leer help set: -utrata las referencias a variables no establecidas como errores.
mklement0
1
entonces es uno set -uo set -eno ambos? @lumpynose
ericn
1
@eric Me retiré hace varios años. A pesar de que amaba mi trabajo, mi viejo cerebro se ha olvidado de todo. De improviso, supongo que podrían usar ambos juntos; mala redacción de mi parte; Debería haber dicho "y / o".
lumpynose
3

Una expresión como

dosomething1 && dosomething2 && dosomething3

detendrá el procesamiento cuando uno de los comandos regrese con un valor distinto de cero. Por ejemplo, el siguiente comando nunca imprimirá "hecho":

cat nosuchfile && echo "done"
echo $?
1
gabor
fuente
2
#!/bin/bash -e

Debería ser suficiente.

Baligh Uddin
fuente
-2

simplemente agregando otro para referencia ya que había una pregunta adicional a la entrada de Mark Edgars y aquí hay un ejemplo adicional y toca el tema general:

[[ `cmd` ]] && echo success_else_silence

que es lo mismo cmd || exit errcodeque alguien mostró.

p.ej. Quiero asegurarme de que una partición esté desmontada si está montada:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 
Malina
fuente
55
No, [[ cmd`]] `no es lo mismo. Es falso si la salida del comando está vacía y verdadera de lo contrario, independientemente del estado de salida del comando.
Gilles 'SO- deja de ser malvado'