salir del script de shell desde un subshell

30

Considere este fragmento:

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if false; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

Normalmente cuando funcse llama provocará que el script finalice, que es el comportamiento previsto. Sin embargo, si se ejecuta en un sub-shell, como en

result=`func`

no saldrá del script. Esto significa que el código de llamada tiene que verificar el estado de salida de la función cada vez. Hay alguna manera de evitar esto? ¿Para esto es esto set -e?

Ernest AC
fuente
1
quiero una función "stop" que imprima un mensaje en stderr y detenga el script, pero no se detiene cuando la función que llama a stop se ejecuta en un sub-shell, como en el ejemplo
Ernest AC
2
Por supuesto, porque sale del subshell no del actual. Simplemente llame a la función directamente: func.
1
no puedo llamarlo directamente porque devuelve una cadena que debe almacenarse en una variable
Ernest AC
1
@ErnestAC Proporcione todos los detalles en la pregunta original. La función anterior no devuelve una cadena.
1
@htor cambié el ejemplo
Ernest AC

Respuestas:

10

Usted podría matar el intérprete original, ( kill $$) antes de llamar exit, y que probablemente había trabajo. Pero:

  • me parece bastante feo
  • se romperá si tiene una segunda subshell allí, es decir, use una subshell dentro de una subshell.

En su lugar, puede utilizar una de las varias formas de devolver un valor en las Preguntas frecuentes de Bash . La mayoría de ellos no son tan buenos, desafortunadamente. Es posible que se quede atascado buscando errores después de cada llamada a la función ( -etiene muchos problemas ). O eso, o cambia a Perl.

derobert
fuente
55
Gracias. Sin embargo, prefiero cambiar a Python.
Ernest AC
2
Mientras escribo, es el año 2019. Decirle a alguien que "cambie a Perl" es ridículo. Lamento ser polémico, pero ¿le dirías a alguien frustrado con 'C' que cambie a Cobol, que es equivalente a IMO? Como señala Ernest, Python es una opción mucho mejor. Mi preferencia sería Ruby. De cualquier manera, cualquier cosa menos Perl.
Graham Nicholls
38

Podría decidir que el estado de salida 77, por ejemplo, significa salir de cualquier nivel de subshell y hacer

set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR

(
  echo here
  (
    echo there
    (
      exit 12 # not 77, exit only this subshell
    )
    echo ici
    exit 77 # exit all subshells
  )
  echo not here
)
echo not here either

set -Een combinación con ERRtrampas es un poco como una versión mejorada de set -eque le permite definir su propio manejo de errores.

En zsh, las trampas ERR se heredan automáticamente, por lo que no es necesario set -E, también puede definir trampas como TRAPERR()funciones y modificarlas $functions[TRAPERR], comofunctions[TRAPERR]="echo was here; $functions[TRAPERR]"

Stéphane Chazelas
fuente
1
¡Solución interesante! Claramente más elegante que kill $$.
3
Una cosa a tener en cuenta, esta trampa no manejará comandos interpolados, por ejemplo echo "$(exit 77)"; el guión continuará como si hubiéramos escritoecho ""
Warbo
Interesante! ¿Hay suerte en bash (bastante antiguo) que no tenga -E? ¿quizás tengamos que recurrir a definir una trampa en una señal de USUARIO y usar un kill para esa señal? También haré algunas investigaciones ...
Olivier Dulac
¿Cómo saber, cuando no está en una trampa de caparazón secundario, para devolver 1 en lugar de 77?
ceving
7

Como alternativa kill $$, también puede intentarlo kill 0, funcionará en el caso de subcapas anidadas (todas las personas que llaman y el proceso secundario recibirán la señal) ... pero sigue siendo brutal y feo.

Stéphane Gimenez
fuente
2
¿No sería este proceso de matar id 0?
Ernest AC
55
Eso matará a todo el grupo de procesos. Puede golpear cosas que no desea (si, por ejemplo, ha comenzado algunas cosas en segundo plano).
derobert
2
@ErnestAC ve la página de manual kill (2), los pids ≤0 tienen un significado especial.
derobert
0

Prueba esto ...

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if $1; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

echo "shell..."
func $1

echo "subshell..."
result=`func $1`

echo "shell..."
echo "result=$result"

Los resultados que obtengo son ...

# test_exitsubshell true
shell...
foo
subshell...
shell...
result=foo
# test_exitsubshell false
shell...
something went wrong

Notas

  • Parametrizado para permitir que la ifprueba sea trueo false(ver las 2 carreras)
  • Cuando la ifprueba es false, nunca llegamos a la subshell.
DocSalvager
fuente
Esto es muy similar a la idea original sobre la que el usuario estaba publicando y dijo que no funcionó. No creo que esto funcione para el caso subshell. Su prueba utiliza salidas falsas después del caso "shell" y nunca llega al caso de prueba "subshell". Creo que fallaría en ese caso ya que la subshell saldría de la llamada "exit 1" pero no propaga el error a la shell externa.
stuckj
0

(Respuesta específica de Bash) Bash no tiene el concepto de excepciones. Sin embargo, con set -o errexit (o el equivalente: set -e) en el nivel más externo, el comando fallido hará que la subshell salga con un estado de salida distinto de cero. Si se trata de un conjunto de subcapas anidadas sin condicionales en torno a la ejecución de esas subcapas, efectivamente 'enrollará' todo el script y saldrá.

Esto puede ser complicado cuando se trata de incluir bits de varios códigos bash en un script más grande. Una parte de bash puede funcionar bien por sí sola, pero cuando se ejecuta bajo errexit (o sin errexit), se comporta de manera inesperada.

[192.168.13.16 (f0f5e19e) ~ 22:58:22] # bash -o errexit / tmp / foo
algo salió mal
[192.168.13.16 (f0f5e19e) ~ 22:58:31] # bash / tmp / foo
algo salió mal
Pero llegamos de todos modos
[192.168.13.16 (f0f5e19e) ~ 22:58:37] # cat / tmp / foo
#! / bin / bash
detener () {
    echo "$ {1}"
    salida 1
}

si es falso luego
    echo "foo"
más
    (
        deja de "algo salió mal"
    )
    echo "Pero llegamos de todos modos"
fi
[192.168.13.16 (f0f5e19e) ~ 22:58:40] #
Brian Chrisman
fuente
-2

Mi ejemplo para salir en un trazador de líneas:

COMAND || ( echo "ERROR – executing COMAND, exiting..." ; exit 77 );[ "$?" -eq 77 ] && exit
vicente
fuente
1
Esto no parece ser realmente una respuesta que funcione con el comando ejecutándose en subcapas como lo solicitó OP ... Eso dijo mientras no estoy en desacuerdo con los votos negativos dados la respuesta. Los votos negativos sin comentarios o razones son tan inútiles como las malas respuestas.
DVS