Estoy observando un comportamiento extraño cuando uso set -e
( errexit
), set -u
( nounset
) junto con trampas ERR y EXIT. Parecen relacionados, por lo que ponerlos en una pregunta parece razonable.
1) set -u
no activa trampas ERR
Código:
#!/bin/bash trap 'echo "ERR (rc: $?)"' ERR set -u echo ${UNSET_VAR}
- Esperado: se llama la trampa ERR, RC! = 0
- Real: no se llama la trampa ERR , RC == 1
- Nota:
set -e
no cambia el resultado
2) Usar set -eu
el código de salida en una trampa EXIT es 0 en lugar de 1
Código:
#!/bin/bash trap 'echo "EXIT (rc: $?)"' EXIT set -eu echo ${UNSET_VAR}
- Esperado: se llama la trampa EXIT, RC == 1
- Real: se llama a EXIT trap, RC == 0
- Nota: Cuando se usa
set +e
, el RC == 1. La trampa EXIT devuelve el RC apropiado cuando cualquier otro comando arroja un error. - Editar: hay una publicación SO sobre este tema con un comentario interesante que sugiere que esto podría estar relacionado con la versión Bash que se está utilizando. Probar este fragmento con Bash 4.3.11 da como resultado un RC = 1, por lo que es mejor. Lamentablemente, la actualización de Bash (desde 3.2.51) en todos los hosts no es posible en este momento, por lo que tenemos que encontrar alguna otra solución.
¿Alguien puede explicar cualquiera de estos comportamientos?
La búsqueda de estos temas no tuvo mucho éxito, lo cual es bastante sorprendente dada la cantidad de publicaciones en la configuración y trampas de Bash. Sin embargo, hay un hilo en el foro , pero la conclusión es bastante insatisfactoria.
bash
rompió con el estándar y comenzó a poner trampas en subcapas. Se supone que la trampa se ejecuta en el mismo entorno de donde vino el regreso, perobash
no lo ha hecho durante bastante tiempo.set -e
yset -u
ambos están diseñados específicamente para matar un shell con script. Usarlos en condiciones que puedan desencadenar su aplicación matará un shell con script. No hay forma de evitar eso, excepto para no usarlos, y en su lugar para probar esas condiciones cuando se aplican en una secuencia de código. Entonces, básicamente, puedes escribir un buen código de shell, o puedes usarloset -eu
.-u
qué no dispararía la trampa ERR (es un error, así que no debería disparar la trampa) o el código de error es 0 en lugar de 1. El Esto último parece ser un error que ya se ha solucionado en una versión posterior, así que eso es todo. Pero la primera parte es bastante difícil de entender si no se ha dado cuenta de que los errores en la evaluación de shell (expansión de parámetros) y los errores reales en los comandos parecen ser dos cosas diferentes. Para la solución, bueno, como sugirió, ahora estoy tratando de evitar-eu
y verificar manualmente cuando sea necesario.(set -u; : $UNSET_VAR)
y similar. Este tipo de cosas también pueden ser buenas, de vez en cuando puedes soltar muchas&&
:(set -e; mkdir dir; cd dir; touch dirfile)
si me entiendes. Es solo que esos son contextos controlados: cuando los configura como opciones globales, pierde el control y se vuelve controlado. Sin embargo, generalmente hay soluciones más eficientes.Respuestas:
De
man bash
:set -u
"@"
y"*"
como un error al realizar la expansión de parámetros. Si se intenta la expansión en una variable o parámetro no establecido, el shell imprime un mensaje de error y, si no es-i
interactivo, sale con un estado distinto de cero.POSIX declara que, en caso de un error de expansión , una shell no interactiva debe salir cuando la expansión está asociada con un shell incorporado especial (que es una distinción que
bash
se ignora regularmente de todos modos, por lo que tal vez sea irrelevante) o cualquier otra utilidad además ."${x!y}"
, porque!
no es un operador válido) ; una implementación puede tratarlos como errores de sintaxis si puede detectarlos durante la tokenización, en lugar de durante la expansión.También de
man bash
:trap ... ERR
while
ountil
...if
declaración ...&&
o||
excepto el comando que sigue al final&&
o||
...!
.-e
.Tenga en cuenta que la trampa ERR se trata de la evaluación del retorno de algún otro comando. Pero cuando se produce un error de expansión , no se ejecuta ningún comando para devolver nada. En su ejemplo,
echo
nunca sucede , porque mientras el shell evalúa y expande sus argumentos, encuentra una-u
variable nset, que ha sido especificada por la opción de shell explícito para provocar una salida inmediata del shell actual con script.Y así, la trampa EXIT , si la hay, se ejecuta, y el shell sale con un mensaje de diagnóstico y un estado de salida distinto de 0, exactamente como debería hacerlo.
En cuanto a la cosa rc: 0 , espero que sea un error específico de la versión de algún tipo, probablemente relacionado con los dos desencadenantes para la SALIDA que se producen al mismo tiempo y el que obtiene el código de salida del otro (que no debería ocurrir) . Y de todos modos, con un
bash
binario actualizado según lo instalado porpacman
:He añadido la primera línea para que pueda ver que las condiciones de la concha son los de una concha con guión - es no interactivo. El resultado es:
Aquí hay algunas notas relevantes de registros de cambios recientes :
$?
correctamente.for
comandos tuvieran el número de línea incorrectotrap
resolvieran en los comandos de subshell asíncronos.trap
controladores para esas señales, y permite que la mayoría de lostrap
controladores se ejecuten de forma recursiva (ejecutandotrap
controladores mientrastrap
se ejecuta un controlador) .Creo que es el último o el primero lo más relevante, o posiblemente una combinación de los dos. Un
trap
manejador es, por su propia naturaleza, asíncrono porque todo su trabajo es esperar y manejar señales asíncronas . Y disparas dos simultáneamente con-eu
y$UNSET_VAR
.Entonces, tal vez deberías actualizar, pero si te gustas, lo harás con un shell completamente diferente.
fuente
(Estoy usando bash 4.2.53). Para la parte 1, la página de manual de bash solo dice "Se escribirá un mensaje de error en el error estándar y se cerrará un shell no interactivo". No dice que se llamará una trampa ERR, aunque estoy de acuerdo en que sería útil si lo hiciera.
Para ser pragmático, si lo que realmente quiere es hacer frente de manera más limpia a las variables indefinidas, una posible solución es colocar la mayor parte de su código dentro de una función, luego ejecutar esa función en un sub-shell y recuperar el código de retorno y la salida stderr. Aquí hay un ejemplo donde "cmd ()" es la función:
En mi fiesta consigo
fuente