2010-11-01 9 views
23

set -e (o un script que comience por #!/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."set -e" en una función

¿Cómo obtengo el equivalente de esto dentro de una función?

Por ejemplo, tengo la siguiente secuencia de comandos que sale inmediatamente en caso de error con un código de salida de error:

#!/bin/sh -e 

echo "the following command could fail:" 
false 
echo "this is after the command that fails" 

La salida es el esperado:

the following command could fail: 

Ahora me gustaría para envolver 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" 

Resultados previstos:

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 set -e)

Se espera que este comportamiento sea el esperado. Mi pregunta es: ¿cómo obtengo un efecto y uso sin set -e dentro de una función de shell? He encontrado the same question preguntado fuera de Stack Overflow pero no hay una respuesta adecuada.

Respuesta

1

Unir todos los comandos en su función con el operador &&. No es demasiado problema y dará el resultado que desee.

+0

Sí, me doy cuenta de que puedo hacer esto, pero no me gusta. Es complicado, es muy fácil perder uno al modificar la función más tarde, y se complica cuando se trata de otras construcciones de shell y con flujo de control. El objetivo de 'set -e' es evitar tener que hacer esto. Sin embargo, aceptaré la respuesta "¡no existe tal mecanismo!" –

1

Sé que esto no es lo que ha preguntado, pero puede o no ser consciente de que el comportamiento que busca 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.

11

De la documentación de set -e:

Cuando esta opción está activada, si un simple comando falla por alguna de las razones enumeradas en Consecuencias de los errores de concha o devuelve un estado de salida valor> 0, y no forma parte de la lista de compuestos después de un tiempo, hasta, o si palabra clave, y no es una parte de una lista AND o O, y no es una tubería precedida por! palabra reservada , entonces el shell deberá inmediatamente salir .

En su caso, false es una parte de un oleoducto precedido 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í. Proveedores:

set -e 
! { false; echo hi; } 
+0

No hay una tubería. Es un comando * compuesto * en oposición a un comando * simple *. –

+1

There * is * a pipeline, aunque es uno degenerado. Si comprueba con el estándar (particularmente "2.10.2 Reglas gramaticales de shell"), verá que una canalización consta de al menos un comando (que puede ser simple, compuesto de func.def.) Precedido por un signo bang opcional. No hay otra manera de introducir una negación excepto la tubería. –

+0

Ahora, * ser parte de * es realmente ambiguo. Uno podría esperar (y lo hice) que esto se refiere a partes inmediatas. En esta interpretación, "falso" sería una parte del grupo de llaves, y el grupo de llaves sería una parte de la tubería, pero 'falso 'no formaría parte de la tubería. Pero, aparentemente, todas las implementaciones que he compartido comparten otra opinión, en la que una * parte * de una declaración puede estar incrustada sin embargo en esta afirmación. –

1

Tendrá que llamar a su función en una cáscara de secundaria (entre paréntesis ()) para lograr esto.

creo que quiere escribir el script de la siguiente manera:

#!/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" 

A continuación, 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 
+3

Mis experimentos muestran que usar una llamada subshell (my_function) no ayuda aquí. Acaba de mover la ejecución my_function fuera de la instrucción if y esto ha ayudado. Volví a abrir esta pregunta aquí http://stackoverflow.com/questions/5754845/set-e-in-a-function. Por favor, eche un vistazo si conoce la respuesta correcta. Lo aprecio. – agsamek

+2

Cuando ejecuto el script anterior, solo obtengo esta única línea de salida: 'el siguiente comando podría fallar:'. El script parece cerrarse completamente en el comando 'false', lo que hace imposible implementar el manejo de errores. Intenté usar bash y dash. –

7

Esto es un poco de una chapuza, pero se puede 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 finaliza la secuencia de comandos. El eco después del falso en f no se ejecutará, ni el cuerpo del if, pero las sentencias posteriores al if se ejecutarán.

Si está utilizando una cáscara que no admite -f exportación, puede obtener la semántica que desee mediante la ejecución de SH en la función:

 
f() { sh -ec ' 
    echo This will execute 
    false 
    echo This will not 
    ' 
} 
+0

Solo para aclarar, ya que '-f f' parecía confuso. Si tengo una función 'whatsamacallit', ¿usaré' sh -ec whatsamacallit' para que la función vuelva en el primer error? –

+0

@Shurane Sí, reemplace 'f' con el nombre de la función. –

6

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.

Desactive set -e, luego ejecute la llamada de función dentro de una subshell que tiene habilitado set -e. 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 se puede ejecutar f como parte de una tubería, o como parte de una lista de &&|| de comandos (excepto que la última orden en el tubo o lista), o como la condición en una o ifwhile u otros contextos que ignoran 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 set -e, en el sentido de que no activará los errores en los contextos en los que set -e no saldrá de los comandos fallidos.

Se podría pensar que el siguiente iba a funcionar, pero no es así:

if ! (set -e; f);then ##### doesn't work, f runs ignoring -e 
    echo "f returned false: $?" 
fi 

set -e no entra en vigor dentro de la subcapa porque recuerda que hay 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.

+1

Nota: Esto no funcionará cuando esto es parte de una función que se usa de nuevo con 'if',' || ',' && ', etc. :( – jomo

+0

forma más agradable de escribir la prueba:' if ((EXIT)), luego echo "f failed"; fi'. Los contextos aritméticos son bastante buenos para hacer cosas booleanas. –

7

Puede usar directamente una subcadena 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 declaración adicional else.

# 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 
+0

Interesante. 'Si ret = $ (set -e; f); then' sale' f' early con 'dash -e', pero no con 'bash -e'. Define' f() {echo a; false; echo "NO debe obtener AQUÍ";} ', ya que quería probarlo con funciones normalmente definidas, no subshell funciones. –

1

Esto es por diseño y especificación POSIX. Podemos leer en man bash:

Si una función de comando compuesto o shell ejecuta en un contexto en el que está siendo ignorada -e, se verá afectado ninguno de los comandos ejecutados dentro del cuerpo de comando compuesto o función por el ajuste -e, incluso si -e está configurado 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 el confiar en set -e dentro de las funciones.

dado el ejemplo siguiente Austin Group:

set -e 
start() { 
    some_server 
    echo some_server started successfully 
} 
start || echo >&2 some_server failed 

la set -e se ignora dentro de la función, ya que la función es un comando en un AND-OR lista distinta de la última.

El comportamiento anterior se especifica y requerido por POSIX (véase: Desired Action):

El ajuste -e se ignora cuando la ejecución de la lista siguiente compuesto la, until, if, o elif palabra reservada while, una canalización que comienza con la palabra reservada !, o cualquier comando de una lista AND-O que no sea la última.

Cuestiones relacionadas