¿Qué significa set -e en un script bash?

713

Estoy estudiando el contenido de este archivo de preinst que el script ejecuta antes de que el paquete se desempaquete de su archivo de Debian (.deb).

El script tiene el siguiente código:

#!/bin/bash
set -e
# Automatically added by dh_installinit
if [ "$1" = install ]; then
   if [ -d /usr/share/MyApplicationName ]; then
     echo "MyApplicationName is just installed"
     return 1
   fi
   rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
   rm -Rf $HOME/.local/share/file-manager/actions/*
fi
# End automatically added section

Mi primera consulta es sobre la línea:

set -e

Creo que el resto del script es bastante simple: comprueba si el administrador de paquetes Debian / Ubuntu está ejecutando una operación de instalación. Si es así, comprueba si mi aplicación acaba de instalarse en el sistema. Si es así, el script imprime el mensaje "MyApplicationName se acaba de instalar" y finaliza ( return 1significa que termina con un "error", ¿no?).

Si el usuario solicita al sistema de paquetes Debian / Ubuntu que instale mi paquete, el script también elimina dos directorios.

¿Es correcto o me estoy perdiendo algo?

AndreaNobili
fuente
42
set -e
Anders Lindahl
46
razón por la que no pudo encontrar esto en google: -e en su consulta se interpreta como negación. Trate siguiente consulta: Conjunto fiesta "-e"
Maleev
3
@twalberg Cuando me hice la misma pregunta, estaba mirandoman set
Sedat Kilinc el
44
si está buscando cómo apagarlo, cambie el guión a un prefijo más:set +e
Tom Saleeba
@twalberg, pero preguntar a personas reales es mucho más interesante que simplemente hacer una solicitud de un robot ;-).
vdegenne

Respuestas:

797

De help set:

  -e  Exit immediately if a command exits with a non-zero status.

Pero algunos lo consideran una mala práctica (preguntas frecuentes sobre bash y autores de preguntas frecuentes sobre irc freenode #bash). Se recomienda usar:

trap 'do_something' ERR

para ejecutar la do_somethingfunción cuando se producen errores.

Ver http://mywiki.wooledge.org/BashFAQ/105

Gilles Quenot
fuente
14
¿Qué sería hacer algo si quisiera la misma semántica que "Salir inmediatamente si un comando sale con un estado distinto de cero"?
CMCDragonkai
71
trap 'exit' ERR
chepner
12
La ERRtrampa no es heredada por las funciones de shell, por lo que si tiene funciones, set -o errtraceo set -Ele permitirá establecer la trampa una vez y aplicarla globalmente.
ykay
31
no trap 'exit' ERRhacer nada diferente de set -e?
Andy
22
Si es una mala práctica, ¿por qué se usa en los paquetes Debian ?
phuclv
98

set -edetiene la ejecución de un script si un comando o canalización tiene un error, que es lo opuesto al comportamiento predeterminado del shell, que es ignorar los errores en los scripts. Escriba help setun terminal para ver la documentación de este comando incorporado.

Robin Green
fuente
45
Solo detiene la ejecución si el último comando en una tubería tiene un error. Hay una opción específica de Bash, set -o pipefailque se puede usar para propagar errores, de modo que el valor de retorno del comando de canalización no sea cero si uno de los comandos anteriores salió con un estado distinto de cero.
Anthony Geoghegan
2
Tenga en cuenta que eso -o pipefailsolo significa que el estado de salida del primer -o errexitcomando distinto de cero (es decir, error en términos) de la tubería se propaga hasta el final. Los comandos restantes en la tubería aún se ejecutan , incluso con set -o errexit. Por ejemplo: echo success | cat - <(echo piping); echo continues, donde echo successrepresenta un éxito, pero comando falibles, se imprimirá success, pipingy continues, sin embargo false | cat - <(echo piping); echo continues, la falseque representa el comando ahora erroring en silencio, todavía se imprima pipingantes de salir.
bb010g
55

Según bash: el manual Set Builtin , si -e/ errexitestá configurado, el shell sale inmediatamente si una tubería que consiste en un solo comando simple , una lista o un comando compuesto devuelve un estado distinto de cero.

Por defecto, el estado de salida de una tubería es el estado de salida del último comando en la tubería, a menos que la pipefailopción esté habilitada (está deshabilitada de manera predeterminada).

Si es así, el estado de retorno de la tubería del último comando (el más a la derecha) para salir con un estado distinto de cero, o cero si todos los comandos salen con éxito.

Si desea ejecutar algo al salir, intente definir trap, por ejemplo:

trap onexit EXIT

¿Dónde onexitestá su función para hacer algo al salir, como a continuación, que está imprimiendo el rastro simple de la pila :

onexit(){ while caller $((n++)); do :; done; }

Hay una opción similar -E/errtrace que atraparía en ERR en su lugar, por ejemplo:

trap onerr ERR

Ejemplos

Ejemplo de estado cero:

$ true; echo $?
0

Ejemplo de estado distinto de cero:

$ false; echo $?
1

Ejemplos de estado de negación:

$ ! false; echo $?
0
$ false || true; echo $?
0

Prueba con pipefailser deshabilitado:

$ bash -c 'set +o pipefail -e; true | true | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; true | true | false; echo success'; echo $?
1

Prueba con pipefailestar habilitado:

$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1
kenorb
fuente
55

Encontré esta publicación mientras intentaba averiguar cuál era el estado de salida de un script que fue abortado debido a un set -e. La respuesta no me pareció obvia; De ahí esta respuesta. Básicamente, set -eaborta la ejecución de un comando (por ejemplo, un script de shell) y devuelve el código de estado de salida del comando que falló (es decir, el script interno, no el script externo) .

Por ejemplo, supongamos que tengo el script de shell outer-test.sh:

#!/bin/sh
set -e
./inner-test.sh
exit 62;

El código para inner-test.shes:

#!/bin/sh
exit 26;

Cuando ejecuto outer-script.shdesde la línea de comandos, mi script externo termina con el código de salida del script interno:

$ ./outer-test.sh
$ echo $?
26
entpnerd
fuente
10

Creo que la intención es que el guión en cuestión falle rápidamente.

Para probar esto usted mismo, simplemente escriba set -een un indicador de bash. Ahora, intenta correr ls. Obtendrá un listado de directorio. Ahora, escribe lsd. Ese comando no se reconoce y devolverá un código de error, por lo que su solicitud de bash se cerrará (debido a set -e).

Ahora, para comprender esto en el contexto de un 'script', use este script simple:

#!/bin/bash 
# set -e

lsd 

ls

Si lo ejecuta como está, obtendrá el listado del directorio lsen la última línea. Si descomenta set -ey vuelve a ejecutar, no verá la lista del directorio ya que bash detiene el procesamiento una vez que encuentra el error delsd .

Kallin Nagelberg
fuente
¿Esta respuesta agrega alguna información o información que ya no se dio en otros sobre la pregunta?
Charles Duffy
66
Creo que ofrece una explicación clara y sucinta de la funcionalidad que no está presente en las otras respuestas. Nada adicional, solo más enfocado que las otras respuestas.
Kallin Nagelberg
9

Esta es una vieja pregunta, pero ninguna de las respuestas aquí discute el uso de set -eaka set -o errexiten los scripts de manejo de paquetes de Debian. El uso de esta opción es obligatorio. en estos scripts, según la política de Debian; la intención aparentemente es evitar cualquier posibilidad de una condición de error no controlada.

Lo que esto significa en la práctica es que debe comprender en qué condiciones los comandos que ejecuta podrían devolver un error y manejar cada uno de esos errores explícitamente.

Los errores comunes son, por ejemplo, diff(devuelve un error cuando hay una diferencia) y grep(devuelve un error cuando no hay coincidencia). Puede evitar los errores con el manejo explícito:

diff this that ||
  echo "$0: there was a difference" >&2
grep cat food ||
  echo "$0: no cat in the food" >&2

(Observe también cómo nos encargamos de incluir el nombre del script actual en el mensaje y de escribir mensajes de diagnóstico en un error estándar en lugar de en la salida estándar).

Si no es realmente necesario o útil un manejo explícito, explícitamente no haga nada:

diff this that || true
grep cat food || :

(El uso de la cáscara : comando no-op es un poco oscuro, pero se ve con bastante frecuencia).

Solo para reiterar,

something || other

es taquigrafía para

if something; then
    : nothing
else
    other
fi

es decir, explícitamente decimos que otherdebe ejecutarse si y solo si somethingfalla. La escritura manual if(y otras declaraciones de control de flujo de shell como while, until) también es una forma válida de manejar un error (de hecho, si no fuera así, los scripts de shell conset -e nunca podrían contener declaraciones de control de flujo!)

Y también, solo para ser explícito, en ausencia de un controlador como este, set -ecausaría que todo el script falle inmediatamente con un error si diffencuentra una diferencia o si grepno encuentra una coincidencia.

Por otro lado, algunos comandos no producen un estado de salida de error cuando desea que lo hagan. Los comandos comúnmente problemáticos son find(el estado de salida no refleja si realmente se encontraron archivos) y sed(el estado de salida no revelará si el script recibió alguna entrada o si realizó algún comando con éxito). Una protección simple en algunos escenarios es canalizar a un comando que grita si no hay salida:

find things | grep .
sed -e 's/o/me/' stuff | grep ^

Cabe señalar que el estado de salida de una tubería es el estado de salida del último comando en esa tubería. Entonces, los comandos anteriores enmascaran completamente el estado de findy sed, y solo le dicen si grepfinalmente tuvo éxito.

(Bash, por supuesto, sí set -o pipefail; pero los scripts de paquetes de Debian no pueden usar las funciones de Bash. La política dicta firmemente el uso de POSIX shpara estos scripts, aunque esto no siempre fue así).

En muchas situaciones, esto es algo a tener en cuenta por separado cuando se codifica a la defensiva. A veces tiene que, por ejemplo, pasar por un archivo temporal para poder ver si el comando que produjo esa salida finalizó con éxito, incluso cuando la expresión idiomática y la conveniencia de otro modo le indicarían que usara una tubería de shell.

tripleee
fuente
Esta es una excelente respuesta. y promueve las mejores prácticas. Tuve exactamente el mismo problema con el comando GREP y realmente no quería eliminar el 'set -e'
Minnie
7
Script 1: without setting -e
#!/bin/bash
decho "hi"
echo "hello"
This will throw error in decho and program continuous to next line

Script 2: With setting -e
#!/bin/bash
set -e
decho "hi" 
echo "hello"
# Up to decho "hi" shell will process and program exit, it will not proceed further
Manikandan Raj
fuente