Tengo un script que no sale cuando lo quiero.
Un script de ejemplo con el mismo error es:
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
echo '2'
Supongo que vería la salida:
:~$ ./test.sh
1
:~$
Pero en realidad veo:
:~$ ./test.sh
1
2
:~$
¿El ()comando encadenamiento de alguna manera crea un alcance? ¿De qué exitsale, si no del guión?
shell-script
exit
subshell
Minix
fuente
fuente

Respuestas:
()ejecuta comandos en el subshell, por loexitque saldrá del subshell y volverá al shell principal. Use llaves{}si desea ejecutar comandos en el shell actual.Del manual bash:
Vale la pena mencionar que la sintaxis del shell es bastante consistente y el subshell participa también en las otras
()construcciones, como la sustitución de comandos (también con la`..`sintaxis de estilo antiguo ) o la sustitución de procesos, por lo que tampoco se saldrá del shell actual:Si bien puede ser obvio que las subcapas están involucradas cuando los comandos se colocan explícitamente dentro
(), el hecho menos visible es que también se generan en estas otras estructuras:comando comenzó en segundo plano
no sale del shell actual porque (después
man bash)la tubería
todavía sale solo de la subshell.
Sin embargo, los diferentes depósitos se comportan de manera diferente a este respecto. Por ejemplo,
bashcoloca todos los componentes de la tubería en subcapas separadas (a menos que use lalastpipeopción en invocaciones donde el control de trabajo no está habilitado), pero AT&Tkshyzshejecuta la última parte dentro del shell actual (POSIX permite ambos comportamientos). Asíhace prácticamente nada en bash, pero sale del zsh debido a la última
exit.coproc exitTambién se ejecutaexiten una subshell.fuente
{y}no son sintaxis, son palabras reservadas y deben estar rodeadas de espacios, y la lista debe terminar con un terminador de comando (punto y coma, nueva línea, ampersand)(echo $$)imprime el ID del shell principal porque$$se expande incluso antes de crear el subshell. De hecho, la identificación del proceso de subshell de impresión podría ser difícil, consulte stackoverflow.com/questions/9119885/…$$se expanda antes de crear la subshell y, sin embargo,$BASHPIDmuestre el valor correcto para una subshell?Ejecutar el
exiten una subshell es una trampa:El script imprime 42, sale de la subshell con el código de retorno
1y continúa con el script. Incluso reemplazar la llamada porecho $(CALC) || exit 1no ayuda porque el código de retorno deechoes 0 independientemente del código de retorno decalc. Ycalcse ejecuta antes deecho.Aún más desconcertante es frustrar el efecto
exitenvolviéndolo en unlocalbuiltin 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 elfnamevalor de retorno dedatepara verificar la validez de la entrada del usuario: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:La línea
fname…dice que los datos de entrada incorrectos se han detectado en la subshell. Pero elexit 1final de lalocal …línea nunca se activa porque lalocaldirectiva siempre regresa0. Esto se debe a quelocalse ejecuta después$(fname)y, por lo tanto, sobrescribe su código de retorno. Y debido a eso, el script continúa e invocatouchcon 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
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.
fuente
doit().La solución real:
La agrupación de errores solo se ejecutará si
bladevuelve un estado de error, yexitno está en una subshell, por lo que todo el script se detiene.fuente
Los corchetes comienzan una subshell y la salida solo sale de esa subshell.
Puede leer el código de salida con
$?y agregar esto en su secuencia de comandos para salir de la secuencia de comandos si se salió de la subshell:fuente