Ejecutar el exiten 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 1y continúa con el script. Incluso reemplazar la llamada por echo $(CALC) || exit 1no ayuda porque el código de retorno de echoes 0 independientemente del código de retorno de calc. Y calcse ejecuta antes de echo.
Aún más desconcertante es frustrar el efecto exitenvolviéndolo en un localbuiltin 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.logpara hoy. La fecha es ingresada por un usuario que puede no proporcionar un valor razonable. Por lo tanto, en mi función verifico el fnamevalor de retorno de datepara 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.logse 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 1final de la local …línea nunca se activa porque la localdirectiva siempre regresa 0. Esto se debe a que localse ejecuta después $(fname) y, por lo tanto, sobrescribe su código de retorno. Y debido a eso, el script continúa e invoca touchcon 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 localdentro 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.