¿Cómo obtengo el valor de salida y salida de una subshell cuando uso "bash -e"?

70

Considere el siguiente código

external-scope.sh

#!/bin/bash
set -e
source inner-scope.sh
echo $(inner)
echo "I thought I would've died :("

inner-scope.sh

#!/bin/bash
function inner() { echo "winner"; return 1; }

Estoy tratando de outer-scope.shsalir cuando inner()falla una llamada . Dado que $()invoca un sub-shell, esto no sucede.

¿De qué otra manera obtengo el resultado de una función al tiempo que preservo el hecho de que la función puede salir con un código de salida distinto de cero?

jabalsad
fuente

Respuestas:

102

$()conserva el estado de salida; solo tiene que usarlo en una declaración que no tenga un estado propio, como una asignación.

salida = $ (interior)

Después de esto, $?contendría el estado de salida de inner, y puede usar todo tipo de comprobaciones para ello:

output=$(inner) || exit $?
echo $output

O:

if ! output=$(inner); then
    exit $?
fi
echo $output

O:

if output=$(inner); then
    echo $output
else
    exit $?
fi

(Nota: un simple exitsin argumentos es equivalente a exit $?, es decir, sale con el estado de salida del último comando. Utilicé el segundo formulario solo para mayor claridad).


Además, para el registro: no sourcetiene ninguna relación en este caso. Simplemente puede definir inner()en el outer-scope.sharchivo, con los mismos resultados.

Gravedad
fuente
¿Por qué es eso, aunque $? contiene el estado de salida de $ () que el script no sale automáticamente (dado que -e está configurado)? EDITAR: no importa, creo que has respondido mis preguntas, ¡gracias!
jabalsad
No estoy seguro. (No he probado ninguno de los anteriores.) Pero hay algunas restricciones en -e, todo explicado en la página de manual de bash; Además, si está preguntando echo $(), podría ser porque los códigos de salida de las subcapas se ignoran cuando la línea, el echocomando, tiene un código de salida (generalmente 0) propio.
Grawity
1
Hmm, cuando escribo if ! $(exit 1) ; then echo $?; fi, me sale 0. No ifestá seguro de cuál es el camino a seguir si necesita preservar ese valor de salida.
Ron Burk
@grawity: ¿esto funciona con Dash? Estoy tratando de evitar un comportamiento molesto de Autoconf (es decir, Autoconf informa de éxito cuando Sun o el compilador de IBM imprime una opción ilegal en el terminal).
jww
3
if ! output=$(inner); then exit $?; fisaldrá con un código de retorno de 0 porque $?dará el código de retorno de en !lugar del código de retorno de inner. Puede obtener el comportamiento deseado, if output=$(inner); then : ; else exit $?; fipero eso es obviamente más detallado
SJL
26

Ver BashFAQ / 002 :

Si desea ambos (salida y estado de salida):

output=$(command)
status=$? 

Un caso especial

Tenga en cuenta un caso complicado con variables locales de función, compare el siguiente código:

f() { local    v=$(echo data; false); echo output:$v, status:$?; }
g() { local v; v=$(echo data; false); echo output:$v, status:$?; }

Nosotros recibiremos:

$ f     # fooled by 'local' with inline initialization
output:data, status:0

$ g     # a good one
output:data, status:1

¿Por qué?

Cuando la salida de una subshell se usa para inicializar una localvariable, el estado de salida ya no es de la subshell, sino del comando , que es más probable que sea .local 0

Ver también https://stackoverflow.com/a/4421282/537554

ryenus
fuente
3
Si bien esto realmente no respondió la pregunta, esto me resultó útil hoy, así que +1.
fourpastmidnight
1
El estado de salida de los comandos bash es siempre el del último comando ejecutado. Cuando pasamos tanto tiempo en idiomas fuertemente tipados, es fácil olvidar que "local" no es un especificador de tipo, sino simplemente otro comando. Gracias por repetir este punto aquí, me ayudó hoy.
markeissler
2
Wow, me encontré con este problema exacto justo ahora y lo resolvió. ¡Gracias!
krb686
4
#!/bin/bash
set -e
source inner-scope.sh
foo=$(inner)
echo $foo
echo "I thought I would've died :("

Al agregar echo, el subshell no está solo (no se verifica por separado) y no se cancela. La asignación evita este problema.

También puede hacer esto y redirigir la salida a un archivo para luego procesarlo.

tmpfile=$( mktemp )
inner > $tmpfile
cat $tmpfile
rm $tmpfile
Daniel Beck
fuente
Por supuesto, el $ tmpfile continúa existiendo en la segunda variante ...
Daniel Beck