shell - script - "Set-e" en una función
shell script example (8)
set -e
(o un script que comienza con #!/bin/sh -e
) es extremadamente útil para bombardear automáticamente si hay un problema. Me ahorra tener que comprobar el error de cada comando que pueda fallar.
¿Cómo obtengo el equivalente de esto dentro de una función?
Por ejemplo, tengo el siguiente script que sale inmediatamente en caso de error con un estado de salida de error:
#!/bin/sh -e
echo "the following command could fail:"
false
echo "this is after the command that fails"
El resultado es el esperado:
the following command could fail:
Ahora me gustaría ajustar esto en una función:
#!/bin/sh -e
my_function() {
echo "the following command could fail:"
false
echo "this is after the command that fails"
}
if ! my_function; then
echo "dealing with the problem"
fi
echo "run this all the time regardless of the success of my_function"
Rendimiento esperado:
the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function
Salida real:
the following output could fail:
this is after the command that fails
run this all the time regardless of the success of my_function
(es decir, la función ignora el set -e
)
Esto, presumiblemente, es el comportamiento esperado. Mi pregunta es: ¿cómo obtengo el efecto y uso sin el set -e
dentro de una función de shell? He encontrado la misma pregunta fuera de Stack Overflow pero no hay una respuesta adecuada.
De la documentación del set -e
:
Cuando esta opción está activada, si falla un comando simple por alguno de los motivos enumerados en Consecuencias de los errores del Shell o devuelve un valor de estado de salida> 0 y no forma parte de la lista compuesta después de un tiempo, hasta, o palabra clave, y no es parte de una lista AND o OR, y no es una tubería precedida por! palabra reservada, entonces el caparazón deberá salir inmediatamente.
En su caso, ¡ false
es una parte de una tubería precedida por !
y una parte de if
. Entonces la solución es reescribir su código para que no lo sea.
En otras palabras, no hay nada especial acerca de las funciones aquí. Tratar:
set -e
! { false; echo hi; }
Esto es por diseño y especificación POSIX. Podemos leer en man bash
:
Si un comando compuesto o función de shell se ejecuta en un contexto donde
-e
se ignora, ninguno de los comandos ejecutados dentro del comando compuesto o cuerpo de función se verá afectado por la configuración-e
, incluso si-e
está establecido y un comando devuelve un estado de falla Si un comando compuesto o función de shell establece-e
mientras se ejecuta en un contexto donde-e
se ignora, esa configuración no tendrá ningún efecto hasta que se complete el comando compuesto o el comando que contiene la llamada a la función.
por lo tanto, debe evitar depender del set -e
dentro de las funciones.
Dado el siguiente ejemplo, Austin Group :
set -e
start() {
some_server
echo some_server started successfully
}
start || echo >&2 some_server failed
el set -e
se ignora dentro de la función, porque la función es un comando en una lista AND-O que no sea la última.
El comportamiento anterior es especificado y requerido por POSIX (ver: Acción deseada ):
La configuración
-e
se ignorará cuando se ejecute la lista compuesta después de la palabra reservadawhile
,until
,if
oelif
, una tubería que comience con!
palabra reservada, o cualquier comando de una lista AND-O que no sea la última.
Esto es un poco complicado, pero puedes hacer:
export -f f if sh -ec f; then ...
Esto funcionará si su shell admite export -f (bash does).
Tenga en cuenta que esto no terminará el script. El eco después de la falsa en f no se ejecutará, ni tampoco se ejecutará el cuerpo de if, sino las sentencias posteriores al if.
Si está utilizando un shell que no admite export -f, puede obtener la semántica que desea ejecutando sh en la función:
f() { sh -ec '' echo This will execute false echo This will not '' }
Finalmente fui con esto, que aparentemente funciona. Probé el método de exportación al principio, pero luego descubrí que necesitaba exportar todas las variables globales (constantes) que usa el script.
Deshabilite set -e
, luego ejecute la llamada a la función dentro de una subshell que ha set -e
enabled. Guarde el estado de salida de la subshell en una variable, vuelva a habilitar set -e, luego pruebe la var.
f() { echo "a"; false; echo "Should NOT get HERE"; }
# Don''t pipe the subshell into anything or we won''t be able to see its exit status
set +e ; ( set -e; f ) ; err_status=$?
set -e
## cleaner syntax which POSIX sh doesn''t support. Use bash/zsh/ksh/other fancy shells
if ((err_status)) ; then
echo "f returned false: $err_status"
fi
## POSIX-sh features only (e.g. dash, /bin/sh)
if test "$err_status" -ne 0 ; then
echo "f returned false: $err_status"
fi
echo "always print this"
No puede ejecutar f
como parte de una interconexión, o como parte de un &&
de ||
lista de comandos (excepto como el último comando en la tubería o lista), o como la condición en un if
o while
, u otros contextos que ignoran el set -e
. Este código tampoco puede estar en ninguno de esos contextos , por lo que si usa esto en una función, las personas que llaman deben usar el mismo truco subshell / save-exit-status. Este uso de set -e
para semántica similar a excepciones de lanzamiento / captura no es realmente adecuado para uso general, dadas las limitaciones y la sintaxis difícil de leer.
trap err_handler_function ERR
tiene las mismas limitaciones que el set -e
, en el sentido de que no activará los errores en contextos donde el set -e
no saldrá de los comandos fallidos.
Puede pensar que lo siguiente funcionaría, pero no:
if ! ( set -e; f );then ##### doesn''t work, f runs ignoring -e
echo "f returned false: $?"
fi
set -e
no tiene efecto dentro de la subshell porque recuerda que está dentro de la condición de un if
. Pensé que ser una subshell cambiaría eso, pero solo funcionaría en un archivo separado y ejecutar un shell completamente separado en él.
Puede usar directamente una subshell como su definición de función y configurarla para que salga inmediatamente con set -e
. Esto limitaría el alcance de set -e
a la función subshell solamente y luego evitaría cambiar entre set +e
y set -e
.
Además, puede usar una asignación de variable en la prueba if
y luego repetir el resultado en una instrucción else
adicional.
# use subshell for function definition
f() (
set -exo pipefail
echo a
false
echo Should NOT get HERE
exit 0
)
# next line also works for non-subshell function given by agsamek above
#if ret="$( set -e && f )" ; then
if ret="$( f )" ; then
true
else
echo "$ret"
fi
# prints
# ++ echo a
# ++ false
# a
Sé que esto no es lo que preguntaste, pero puedes o no ser consciente de que el comportamiento que buscas está integrado en "hacer". Cualquier parte de un proceso "make" que falla aborta la ejecución. Sin embargo, es una forma completamente diferente de "programación", que las secuencias de comandos shell.
Tendrá que llamar a su función en un subconjunto (dentro de los paréntesis () ) para lograr esto.
Creo que quieres escribir tu script así:
#!/bin/sh -e
my_function() {
echo "the following command could fail:"
false
echo "this is after the command that fails"
}
(my_function)
if [ $? -ne 0 ] ; then
echo "dealing with the problem"
fi
echo "run this all the time regardless of the success of my_function"
Entonces la salida es (como se desee):
the following command could fail:
dealing with the problem
run this all the time regardless of the success of my_function
Une todos los comandos de tu función con el operador &&
. No es demasiado problema y dará el resultado que desee.