Solleva un errore in uno script Bash


103

Voglio sollevare un errore in uno script Bash con il messaggio "Test case Failed !!!". Come farlo in Bash?

Per esempio:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi

1
Cosa vuoi che accada a questo errore? Come si chiama il tuo script? È solo uno script o molti script? Quali saranno gli usi del tuo script?
Etan Reisner

solo un copione. L'ho chiamato usando il terminale Ubuntu come ./script/test.sh
Naveen Kumar



Nessun amore per echo you screwed up at ... | mail -s BUG $bugtrackeremailaddress?
infisso

Risposte:


121

Dipende da dove si desidera memorizzare il messaggio di errore.

Puoi fare quanto segue:

echo "Error!" > logfile.log
exit 125

Oppure il seguente:

echo "Error!" 1>&2
exit 64

Quando sollevi un'eccezione, interrompi l'esecuzione del programma.

Puoi anche usare qualcosa come exit xxxdov'è xxxil codice di errore che potresti voler restituire al sistema operativo (da 0 a 255). Qui 125e 64sono solo codici random si può uscire con. Quando è necessario indicare al sistema operativo che il programma si è arrestato in modo anomalo (ad es. Si è verificato un errore), è necessario passare un codice di uscita diverso da zero a exit.

Come ha sottolineato @chepner , puoi farlo exit 1, il che significherà un errore non specificato .


12
oppure potresti inviarlo a stderr, dove dovrebbero andare gli errori.

come inviarlo a stderr?
Naveen Kumar

2
@ user3078630, ho appena modificato la mia risposta. 1>&2farà il trucco
ForceBru

Se si tratta di un errore, dovresti anche uscire con uno stato di uscita diverso da zero. exitda solo utilizza lo stato di uscita del comando completato più di recente, che può essere 0.
chepner

3
A meno che tu non abbia in mente un significato specifico, dovresti semplicemente usare exit 1 , che per convenzione significa un errore non specificato.
chepner

36

Gestione degli errori di base

Se il runner dello scenario di test restituisce un codice diverso da zero per i test non riusciti, 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 degli 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' "$@" >&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 degli errori come exit_if_errorsono:

  • 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 segnali (usando trap ), possiamo invocare il gestore di errori da lì

Libreria di gestione e registrazione degli errori

Ecco un'implementazione completa della gestione e della registrazione degli errori:

https://github.com/codeforester/base/blob/master/lib/stdlib.sh


Post correlati


9

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 controllare se lo script è stato eseguito correttamente e generare errori in caso di errori.

I comandi della shell generalmente si basano sui codici di uscita restituiti per far sapere alla shell se ha avuto successo o è fallito a causa di alcuni eventi imprevisti.

Quindi quello che vuoi fare ricade su queste due categorie

  • uscire in caso di errore
  • uscire e ripulire in caso di errore

A seconda di quale si desidera eseguire, sono disponibili opzioni di shell da utilizzare. Per il primo caso, la shell fornisce un'opzione con set -ee per la seconda si potrebbe fare una trapsuEXIT

Dovrei usare exitnel mio script / funzione?

Utilizzando exit genere migliora la leggibilità In alcune routine, una volta che si conosce la risposta, si desidera uscire immediatamente dalla routine di chiamata. Se la routine è definita in modo tale da non richiedere ulteriori pulizie una volta rilevato un errore, non uscire immediatamente significa che devi scrivere più codice.

Quindi nei casi in cui è necessario eseguire azioni di pulizia sullo script per rendere pulita la terminazione dello script, è preferibile non utilizzare exit.

Devo usare set -eper errore in uscita?

No!

set -eera un tentativo di aggiungere "rilevamento automatico degli errori" alla shell. Il suo obiettivo era far interrompere la shell ogni volta che si verificava un errore, ma presenta molte potenziali insidie, ad esempio,

  • I comandi che fanno parte di un if test sono immuni. Nell'esempio, se ti aspetti che si interrompa durante il testcontrollo sulla directory inesistente, non lo farebbe, passa alla condizione else

    set -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é il codice di uscita del comando eseguito più di recente (più a destra) è considerato ( cat) ed è stato eseguito correttamente. Questo potrebbe essere evitato impostando l' set -o pipefailopzione, ma è ancora un avvertimento.

    set -e
    somecommand that fails | cat -
    echo survived 

Consigliato per l'uso - trapin uscita

Il verdetto è se vuoi essere in grado di gestire un errore invece di uscire alla cieca, invece di usare set -e, usa un trapsullo ERRpseudo segnale.

La ERRtrappola non consiste nell'eseguire 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 è definire un gestore trap per fornire ulteriori informazioni di debug su quale riga e cosa causa l'uscita. Ricorda che il codice di uscita dell'ultimo comando che ha causato il ERRsegnale 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 questo gestore come di seguito sopra lo script che non funziona

trap cleanup ERR

Mettendo questo insieme su un semplice script che conteneva falsealla riga 15, le informazioni che avresti ottenuto come

error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15

La trapoffre anche opzioni a prescindere l'errore basta eseguire la pulizia al termine della shell (per esempio, le vostre uscite di script di shell), il segnale EXIT. Puoi anche eseguire il trapping su 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, nel qual caso potresti dover aggiungere la tua gestione degli errori.

  • Su una sottostruttura con set -enon funzionerebbe. Il falseè limitato alla sub-shell e non viene propagato a shell genitore. Per eseguire la gestione degli errori qui, aggiungi la tua logica da fare(false) || false

    set -e
    (false)
    echo survived
  • Lo stesso accade trapanche con . La logica seguente non funzionerebbe per i motivi sopra menzionati.

    trap 'echo error' ERR
    (false)

5

Ecco una semplice trap che stampa l'ultimo argomento di qualsiasi cosa non sia riuscita a STDERR, segnala la riga su cui non è riuscito ed esce dallo script con il numero di riga come codice di uscita. Nota che queste non sono sempre ottime idee, ma questo dimostra alcune applicazioni creative su cui potresti costruire.

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

L'ho inserito in uno script con un ciclo per testarlo. Controllo solo alcuni numeri casuali; potresti usare test effettivi. Se ho bisogno di una cauzione, chiamo falso (che innesca la trappola) con il messaggio che voglio lanciare.

Per funzionalità elaborate, fare in modo che la trap chiami una funzione di elaborazione. Puoi sempre usare un'istruzione case sul tuo arg ($ _) se hai bisogno di fare più pulizia, ecc. Assegna a una variabile per un po 'di zucchero sintattico -

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false

while :
do x=$(( $RANDOM % 10 ))
   case "$x" in
   0) $throw "DIVISION BY ZERO" ;;
   3) $raise "MAGIC NUMBER"     ;;
   *) echo got $x               ;;
   esac
done

Output di esempio:

# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6

Ovviamente potresti

runTest1 "Test1 fails" # message not used if it succeeds

Molto spazio per il miglioramento del design.

Gli svantaggi includono il fatto che falsenon è carino (quindi lo zucchero), e altre cose che fanno scattare la trappola potrebbero sembrare un po 'stupide. Tuttavia, mi piace questo metodo.


4

Hai 2 opzioni: reindirizzare l'output dello script a un file, introdurre un file di registro nello script e

  1. Reindirizzamento dell'output a un file :

Qui si presume che lo script restituisca tutte le informazioni necessarie, inclusi i messaggi di avviso e di errore. È quindi possibile reindirizzare l'output a un file di propria scelta.

./runTests &> output.log

Il comando precedente reindirizza sia l'output standard che l'output degli errori al file di registro.

Utilizzando questo approccio non è necessario introdurre un file di registro nello script, quindi la logica è un po 'più semplice.

  1. Introduci un file di registro nello script :

Nel tuo script aggiungi un file di registro tramite hard coding:

logFile='./path/to/log/file.log'

o passandolo da un parametro:

logFile="${1}"  # This assumes the first parameter to the script is the log file

È una buona idea aggiungere il timestamp al momento dell'esecuzione al file di registro nella parte superiore dello script:

date '+%Y%-m%d-%H%M%S' >> "${logFile}"

È quindi possibile reindirizzare i messaggi di errore al file di registro

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
fi

Ciò aggiungerà l'errore al file di registro e continuerà l'esecuzione. Se desideri interrompere l'esecuzione quando si verificano errori critici, puoi eseguire exitlo script:

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
    # Clean up if needed
    exit 1;
fi

Nota che exit 1 indica che il programma interrompe l'esecuzione a causa di un errore non specificato. Puoi personalizzarlo se lo desideri.

Utilizzando questo approccio è possibile personalizzare i log e disporre di un file di log diverso per ogni componente dello script.


Se si dispone di uno script relativamente piccolo o si desidera eseguire lo script di qualcun altro senza modificarlo, il primo approccio è più adatto.

Se vuoi sempre che il file di registro si trovi nella stessa posizione, questaèla migliore opzione del 2. Inoltre se hai creato uno script di grandi dimensioni con più componenti allora potresti voler registrare ogni parte in modo diverso e il secondo approccioèunico tuo opzione.


3

Trovo spesso utile scrivere una funzione per gestire i messaggi di errore in modo che il codice sia complessivamente più pulito.

# Usage: die [exit_code] [error message]
die() {
  local code=$? now=$(date +%T.%N)
  if [ "$1" -ge 0 ] 2>/dev/null; then  # assume $1 is an error code if numeric
    code="$1"
    shift
  fi
  echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
  exit $code
}

Questo prende il codice di errore dal comando precedente e lo utilizza come codice di errore predefinito quando si esce dall'intero script. Annota anche l'ora, con i microsecondi dove supportati (la data GNU %Nè nanosecondi, che tronciamo a microsecondi in seguito).

Se la prima opzione è zero o un numero intero positivo, diventa il codice di uscita e lo rimuoviamo dall'elenco delle opzioni. Riportiamo quindi il messaggio all'errore standard, con il nome dello script, la parola "ERRORE" e l'ora (usiamo l'espansione dei parametri per troncare nanosecondi in microsecondi, o per tempi non GNU, per troncare ad esempio 12:34:56.%Na 12:34:56). Dopo la parola ERRORE vengono aggiunti due punti e uno spazio, ma solo quando viene fornito un messaggio di errore. Infine, usciamo dallo script utilizzando il codice di uscita determinato in precedenza, attivando qualsiasi trap normalmente.

Alcuni esempi (supponiamo che il codice risieda script.sh):

if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"

$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"

$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
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.