Ejecutar el exit
en una subshell es una trampa:
#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)
El script imprime 42, sale de la subshell con el código de retorno 1
y continúa con el script. Incluso reemplazar la llamada por echo $(CALC) || exit 1
no ayuda porque el código de retorno de echo
es 0 independientemente del código de retorno de calc
. Y calc
se ejecuta antes de echo
.
Aún más desconcertante es frustrar el efecto exit
envolviéndolo en un local
builtin como en el siguiente script. Me topé con el problema cuando escribí una función para verificar un valor de entrada. Ejemplo:
Quiero crear un archivo llamado "año mes día.log", es decir, 20141211.log
para hoy. La fecha es ingresada por un usuario que puede no proporcionar un valor razonable. Por lo tanto, en mi función verifico el fname
valor de retorno de date
para verificar la validez de la entrada del usuario:
#!/bin/bash
doit ()
{
local FNAME=$(fname "$1") || exit 1
touch "${FNAME}"
}
fname ()
{
date +"%Y%m%d.log" -d"$1" 2>/dev/null
if [ "$?" != 0 ] ; then
echo "fname reports \"Illegal Date\"" >&2
exit 1
fi
}
doit "$1"
Se ve bien. Deje que se nombre el guión s.sh
. Si el usuario llama al script con ./s.sh "Thu Dec 11 20:45:49 CET 2014"
, 20141211.log
se crea el archivo . Sin embargo, si el usuario escribe ./s.sh "Thu hec 11 20:45:49 CET 2014"
, el script genera:
fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory
La línea fname…
dice que los datos de entrada incorrectos se han detectado en la subshell. Pero el exit 1
final de la local …
línea nunca se activa porque la local
directiva siempre regresa 0
. Esto se debe a que local
se ejecuta después $(fname)
y, por lo tanto, sobrescribe su código de retorno. Y debido a eso, el script continúa e invoca touch
con un parámetro vacío. Este ejemplo es simple, pero el comportamiento de bash puede ser bastante confuso en una aplicación real. Lo sé, los programadores reales no usan locales.
Para que quede claro: sin el local
, el script se aborta como se esperaba cuando se ingresa una fecha no válida.
La solución es dividir la línea como
local FNAME
FNAME=$(fname "$1") || exit 1
El extraño comportamiento se ajusta a la documentación local
dentro de la página de manual de bash: "El estado de retorno es 0 a menos que se use local fuera de una función, se proporcione un nombre no válido o el nombre sea una variable de solo lectura".
Aunque no es un error, siento que el comportamiento de bash es contradictorio. Soy consciente de la secuencia de ejecución local
, sin embargo, no debería enmascarar una tarea rota.
Mi respuesta inicial contenía algunas imprecisiones. Después de una discusión reveladora y profunda con mikeserv (gracias por eso) fui a arreglarlos.