esce dallo script di shell da una subshell


30

Considera questo frammento:

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

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

Normalmente quando funcviene chiamato, lo script viene chiuso, ovvero il comportamento previsto. Tuttavia, se viene eseguito in una sotto-shell, come in

result=`func`

non uscirà dallo script. Ciò significa che il codice chiamante deve controllare ogni volta lo stato di uscita della funzione. c'è un modo per evitarlo? È a questo che set -eserve?


1
voglio una funzione "stop" che stampa un messaggio su stderr e arresta lo script, ma non si ferma quando la funzione che chiama stop viene eseguita in una sotto-shell, come nell'esempio
Ernest AC

2
Certo, perché esce dalla subshell non quella corrente. È sufficiente chiamare la funzione direttamente: func.

1
non posso chiamarlo direttamente perché restituisce una stringa che deve essere memorizzata in una variabile
Ernest AC

1
@ErnestAC Fornire tutti i dettagli nella domanda originale. La funzione precedente non restituisce una stringa.

1
@htor ho cambiato l'esempio
Ernest AC

Risposte:


10

Si potrebbe uccidere la shell originale ( kill $$) prima di chiamare exit, e che sarebbe probabilmente il lavoro. Ma:

  • mi sembra piuttosto brutto
  • si interromperà se si dispone di una seconda subshell, ovvero utilizzare una subshell all'interno di una subshell.

Invece, è possibile utilizzare uno dei diversi modi per restituire un valore nelle FAQ di Bash . La maggior parte di loro non è così eccezionale, sfortunatamente. Potresti essere bloccato a controllare gli errori dopo ogni chiamata di funzione ( -eha molti problemi ). O quello, o passare a Perl.


5
Grazie. Preferirei passare a Python, però.
Ernest AC,

2
Mentre scrivo, è l'anno 2019. Dire a qualcuno di "passare al Perl" è ridicolo. Mi dispiace essere controverso, ma diresti a qualcuno frustrato con 'C' di passare a Cobol, che è equivalente IMO? Come sottolinea Ernest, Python è una scelta molto migliore. La mia preferenza sarebbe Ruby. Ad ogni modo, tutt'altro che Perl.
Graham Nicholls

38

Si potrebbe decidere che lo stato di uscita 77, ad esempio, significhi uscire da qualsiasi livello di subshell e farlo

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 -Ein combinazione con le ERRtrappole è un po 'come una versione migliorata di set -ein che consente di definire la propria gestione degli errori.

In zsh, le trap ERR vengono ereditate automaticamente, quindi non è necessario set -E, puoi anche definire trap come TRAPERR()funzioni e modificarle $functions[TRAPERR], comefunctions[TRAPERR]="echo was here; $functions[TRAPERR]"


1
Soluzione interessante! Chiaramente più elegante di kill $$.

3
Una cosa a cui fare attenzione, questa trappola non gestirà i comandi interpolati, ad esempio echo "$(exit 77)"; la sceneggiatura continuerà come se avessimo scrittoecho ""
Warbo il

Interresting! C'è fortuna su bash (piuttosto vecchio) che non ha -E? forse dobbiamo ricorrere alla definizione di una trappola su un segnale USER e all'uso di un kill per quel segnale? Farò anche qualche ricerca ...
Olivier Dulac,

Come sapere, quando non ci si trova in una trappola sub-shell, per restituire 1 anziché 77?
ceving

7

In alternativa kill $$, potresti anche provare kill 0, funzionerà nel caso di sottotitoli annidati (tutti i chiamanti e il processo laterale riceveranno il segnale) ... ma è ancora brutale e brutto.


2
Questo kill process non sarebbe id 0?
Ernest AC,

5
Questo ucciderà l'intero gruppo di processi. Puoi colpire cose che non vuoi (se per esempio hai iniziato alcune cose in background).
derobert,

2
@ErnestAC guarda la manpage kill (2), pids ≤0 hanno un significato speciale.
derobert,

0

Prova questo ...

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"

I risultati che ottengo sono ...

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

Gli appunti

  • Parametrizzato per consentire il iftest trueo false(vedere le 2 esecuzioni)
  • Quando il iftest è false, non raggiungiamo mai la subshell.

Questo è molto simile all'idea originale su cui l'utente stava postando e ha detto che non ha funzionato. Non penso che questo funzioni per il caso subshell. Il tuo test usando false esce dopo il caso "shell" e non arriva mai al caso test "subshell". Credo che fallirebbe per quel caso poiché la subshell uscirebbe dalla chiamata "exit 1" ma non propaga l'errore alla shell esterna.
stuckj,

0

(Risposta specifica di Bash) Bash non ha il concetto di eccezioni. Tuttavia, con set -o errexit (o l'equivalente: set -e) al livello più esterno, il comando non riuscito comporterà l'uscita della shell secondaria con uno stato di uscita diverso da zero. Se si tratta di un insieme di sottotitoli nidificati senza condizionali attorno all'esecuzione di tali sottotitoli, "arrotolerà" effettivamente l'intero script e uscirà.

Questo può essere complicato quando si tenta di includere bit di vari codici bash in uno script più grande. Un pezzo di bash può funzionare bene da solo, ma quando eseguito sotto errexit (o senza errexit), si comporta in modi inaspettati.

[192.168.13.16 (f0f5e19e) ~ 22:58:22] # bash -o errexit / tmp / foo
qualcosa è andato storto
[192.168.13.16 (f0f5e19e) ~ 22:58:31] # bash / tmp / foo
qualcosa è andato storto
Ma siamo arrivati ​​qui comunque
[192.168.13.16 (f0f5e19e) ~ 22:58:37] # cat / tmp / foo
#! / Bin / bash
fermare () {
    echo "$ {1}"
    uscita 1
}

se falso; poi
    eco "pippo"
altro
    (
        stop "qualcosa è andato storto"
    )
    echo "Ma siamo arrivati ​​qui comunque"
fi
[192.168.13.16 (f0f5e19e) ~ 22:58:40] #

-2

Il mio esempio per uscire in una riga:

COMAND || ( echo "ERROR – executing COMAND, exiting..." ; exit 77 );[ "$?" -eq 77 ] && exit

1
Questa non sembra in realtà una risposta che funzionerebbe con il comando in esecuzione in una sotto-shell come richiesto da OP ... Detto questo, mentre non sono in disaccordo con i voti negativi data la risposta. I voti negativi senza commenti o motivi sono altrettanto inutili delle risposte negative.
DVS
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.