Ci sono un altro paio di modi con cui puoi affrontare questo problema. Supponendo che uno dei tuoi requisiti sia eseguire uno script/funzione di shell contenente alcuni comandi di shell e verificare se lo script è stato eseguito correttamente e generare errori in caso di errori.
I comandi della shell in genere si basano sui codici di uscita restituiti per far sapere alla shell se ha avuto successo o meno a causa di alcuni eventi imprevisti.
Quindi quello che vuoi fare rientra in queste due categorie
- uscita in caso di errore
- uscita e pulizia in caso di errore
A seconda di quale vuoi fare, ci sono opzioni di shell disponibili da usare. Per il primo caso, la shell fornisce un'opzione con set -e
e per il secondo potresti fare un trap
il EXIT
Dovrei usare exit
nel mio script/funzione?
Usando exit
generalmente migliora la leggibilità In alcune routine, una volta che si conosce la risposta, si desidera uscire immediatamente dalla routine chiamante. Se la routine è definita in modo tale da non richiedere ulteriori operazioni di pulizia una volta rilevato un errore, non uscire immediatamente significa che devi scrivere altro codice.
Quindi, nei casi in cui è necessario eseguire azioni di pulizia sullo script per rendere pulita la terminazione dello script, è preferibile non per usare exit
.
Dovrei usare set -e
per errore in uscita?
No!
set -e
era un tentativo di aggiungere "rilevamento automatico degli errori" alla shell. Il suo obiettivo era far sì che la shell si interrompesse ogni volta che si verificava un errore, ma presenta molte potenziali insidie, ad esempio,
-
I comandi che fanno parte di un test if sono immuni. Nell'esempio, se ti aspetti che si rompa su
test
controlla la directory inesistente, non lo farebbe, passa alla condizione elseset -e f() { test -d nosuchdir && echo no dir; } f echo survived
-
I comandi in una pipeline diversa dall'ultima sono immuni. Nell'esempio seguente, poiché viene considerato il codice di uscita del comando eseguito più di recente (all'estrema destra) (
cat
) e ha avuto successo. Ciò potrebbe essere evitato impostando ilset -o pipefail
opzione ma è ancora un avvertimento.set -e somecommand that fails | cat - echo survived
Consigliato per l'uso - trap
in uscita
Il verdetto è se vuoi essere in grado di gestire un errore invece di uscire alla cieca, invece di usare set -e
, usa un trap
sul ERR
pseudo segnale.
Il ERR
trap non è eseguire il codice quando la shell stessa esce con un codice di errore diverso da zero, ma quando qualsiasi comando eseguito da quella shell che non fa parte di una condizione (come in if cmd
o cmd ||
) esce con uno stato di uscita diverso da zero.
La pratica generale è che definiamo un gestore di trap per fornire ulteriori informazioni di debug su quale riga e cosa causa l'uscita. Ricorda il codice di uscita dell'ultimo comando che ha causato il ERR
segnale sarebbe ancora disponibile a questo punto.
cleanup() {
exitcode=$?
printf 'error condition hit\n' 1>&2
printf 'exit code returned: %s\n' "$exitcode"
printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
printf 'command present on line: %d' "${BASH_LINENO[0]}"
# Some more clean up code can be added here before exiting
exit $exitcode
}
e usiamo semplicemente questo gestore come sotto in cima allo script che sta fallendo
trap cleanup ERR
Mettere insieme tutto questo su un semplice script che conteneva false
alla riga 15, le informazioni che otterresti come
error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15
Il trap
fornisce anche opzioni indipendentemente dall'errore per eseguire la pulizia solo al completamento della shell (ad es. lo script della shell termina), al segnale EXIT
. Puoi anche intrappolare più segnali contemporaneamente. L'elenco dei segnali supportati su cui eseguire il trap può essere trovato nella trap.1p - pagina di manuale di Linux
Un'altra cosa da notare sarebbe capire che nessuno dei metodi forniti funziona se hai a che fare con sotto-shell coinvolte, nel qual caso potresti dover aggiungere la tua gestione degli errori.
-
Su una sub-shell con
set -e
non funzionerebbe. Ilfalse
è limitato alla sub-shell e non viene mai propagato alla shell madre. Per eseguire la gestione degli errori qui, aggiungi la tua logica per eseguire(false) || false
set -e (false) echo survived
-
Lo stesso accade con
trap
anche. La logica seguente non funzionerebbe per i motivi sopra menzionati.trap 'echo error' ERR (false)
Gestione degli errori di base
Se il tuo test case runner restituisce un codice diverso da zero per i test falliti, puoi semplicemente scrivere:
test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
printf '%s\n' "Test case x failed" >&2 # write error message to stderr
exit 1 # or exit $test_result
fi
O anche più breve:
if ! test_handler test_case_x; then
printf '%s\n' "Test case x failed" >&2
exit 1
fi
O il più breve:
test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }
Per uscire con il codice di uscita di test_handler:
test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }
Gestione avanzata degli errori
Se vuoi adottare un approccio più completo, puoi avere un gestore di errori:
exit_if_error() {
local exit_code=$1
shift
[[ $exit_code ]] && # do nothing if no error code passed
((exit_code != 0)) && { # do nothing if error code is 0
printf 'ERROR: %s\n' "[email protected]" >&2 # we can use better logging here
exit "$exit_code" # we could also check to make sure
# error code is numeric when passed
}
}
quindi invocalo dopo aver eseguito il test case:
run_test_case test_case_x
exit_if_error $? "Test case x failed"
o
run_test_case test_case_x || exit_if_error $? "Test case x failed"
I vantaggi di avere un gestore di errori come exit_if_error
sono:
- possiamo standardizzare tutta la logica di gestione degli errori come la registrazione, la stampa di una traccia dello stack, la notifica, la pulizia ecc., in un unico posto
- facendo in modo che il gestore degli errori ottenga il codice di errore come argomento, possiamo risparmiare il chiamante dalla confusione di
if
blocchi che testano i codici di uscita per gli errori - se abbiamo un gestore di segnale (usando trap), possiamo invocare il gestore degli errori da lì
Libreria di gestione degli errori e registrazione
Ecco un'implementazione completa della gestione e registrazione degli errori:
https://github.com/codeforester/base/blob/master/lib/stdlib.sh
Post correlati
- Gestione degli errori in Bash
- Il comando integrato 'caller' su Bash Hackers Wiki
- Esistono codici di stato di uscita standard in Linux?
- BashFAQ/105 - Perché set -e (o set -o errexit o trap ERR) non fa quello che mi aspettavo?
- Equivalente di
__FILE__
,__LINE__
in Basch - C'è un comando TRY CATCH in Bash
- Per aggiungere una traccia dello stack al gestore degli errori, potresti dare un'occhiata a questo post:Traccia dei programmi eseguiti chiamati da uno script Bash
- Ignorare errori specifici in uno script di shell
- Rilevamento di codici di errore in una shell pipe
- Come gestisco la verbosità dei log all'interno di uno script di shell?
- Come registrare il nome della funzione e il numero di riga in Bash?
- Le parentesi quadre doppie [[ ]] sono preferibili alle parentesi quadre singole [ ] in Bash?
Ciò dipende da dove si desidera memorizzare il messaggio di errore.
Puoi fare quanto segue:
echo "Error!" > logfile.log
exit 125
O il seguente:
echo "Error!" 1>&2
exit 64
Quando sollevi un'eccezione interrompi l'esecuzione del programma.
Puoi anche usare qualcosa come exit xxx
dove xxx
è il codice di errore che potresti voler restituire al sistema operativo (da 0 a 255). Qui 125
e 64
sono solo codici casuali con cui puoi uscire. Quando devi indicare al sistema operativo che il programma si è arrestato in modo anomalo (ad es. si è verificato un errore), devi passare un codice di uscita diverso da zero a exit
.
Come ha sottolineato @chepner, puoi fare exit 1
, che indicherà un errore non specificato .