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é exit
sale, si no del guión?
shell-script
exit
subshell
Minix
fuente
fuente
Respuestas:
()
ejecuta comandos en el subshell, por loexit
que 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,
bash
coloca todos los componentes de la tubería en subcapas separadas (a menos que use lalastpipe
opción en invocaciones donde el control de trabajo no está habilitado), pero AT&Tksh
yzsh
ejecuta 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 exit
También se ejecutaexit
en 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,$BASHPID
muestre el valor correcto para una subshell?Ejecutar el
exit
en una subshell es una trampa: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 porecho $(CALC) || exit 1
no ayuda porque el código de retorno deecho
es 0 independientemente del código de retorno decalc
. Ycalc
se ejecuta antes deecho
.Aún más desconcertante es frustrar el efecto
exit
envolviéndolo en unlocal
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 elfname
valor de retorno dedate
para 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.log
se 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 1
final de lalocal …
línea nunca se activa porque lalocal
directiva siempre regresa0
. Esto se debe a quelocal
se ejecuta después$(fname)
y, por lo tanto, sobrescribe su código de retorno. Y debido a eso, el script continúa e invocatouch
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
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.
fuente
doit()
.La solución real:
La agrupación de errores solo se ejecutará si
bla
devuelve un estado de error, yexit
no 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