Script di shell di uscita basato sul codice di uscita del processo


378

Ho uno script di shell che esegue una serie di comandi. Come faccio a chiudere lo script della shell se uno dei comandi esce con un codice di uscita diverso da zero?


3
Ho risposto supponendo che tu stia usando bash, ma se è una shell molto diversa puoi specificare nel tuo post?
Martin W,

Metodo difficile: testare il valore di $?dopo ogni comando. Metodo semplice: inserisci set -eo #!/bin/bash -enella parte superiore del tuo script Bash.
mwfearnley,

Risposte:


495

Dopo ogni comando, il codice di uscita può essere trovato nella $?variabile in modo da avere qualcosa del tipo:

ls -al file.ext
rc=$?; if [[ $rc != 0 ]]; then exit $rc; fi

Devi stare attento ai comandi piped poiché l' $?unico ti dà il codice di ritorno dell'ultimo elemento nella pipe così, nel codice:

ls -al file.ext | sed 's/^/xx: /"

non restituirà un codice di errore se il file non esiste (poiché il file sed parte della pipeline funziona effettivamente, restituendo 0).

La bashshell in realtà fornisce un array che può aiutare in quel caso, quell'essere PIPESTATUS. Questo array ha un elemento per ciascuno dei componenti della pipeline, a cui puoi accedere individualmente come ${PIPESTATUS[0]}:

pax> false | true ; echo ${PIPESTATUS[0]}
1

Si noti che questo ti sta ottenendo il risultato del falsecomando, non l'intera pipeline. Puoi anche far elaborare l'intero elenco come ritieni opportuno:

pax> false | true | false; echo ${PIPESTATUS[*]}
1 0 1

Se si desidera ottenere il codice di errore più grande da una pipeline, è possibile utilizzare qualcosa del tipo:

true | true | false | true | false
rcs=${PIPESTATUS[*]}; rc=0; for i in ${rcs}; do rc=$(($i > $rc ? $i : $rc)); done
echo $rc

Questo passa attraverso ciascuno degli PIPESTATUSelementi a sua volta, memorizzandolo rcse fosse maggiore del rcvalore precedente .


39
Stessa funzione in una sola riga di codice portatile: ls -al file.ext || exit $?([[]] non è portatile)
Marc

19
MarcH, penso che scoprirai che [[ ]]è abbastanza portatile bash, ed è quello che la domanda è taggata :-) Stranamente, lsnon funziona in command.commodo che non sia portatile, specioso lo so, ma è lo stesso tipo di argomento che tu presente.
paxdiablo,

39
So che questo è antico, ma va notato che è possibile ottenere il codice di uscita dei comandi in una pipe tramite l'array PIPESTATUS(ovvero, ${PIPESTATUS[0]}per il primo comando, ${PIPESTATUS[1]}per il secondo o ${PIPESTATUS[*]}per un elenco di tutti gli stati di uscita.
DevSolar

11
Va sottolineato che lo scripting shell elegante e idiomatico molto raramente deve esaminare $?direttamente. Di solito si desidera qualcosa di simile, if ls -al file.ext; then : nothing; else exit $?; fiovviamente, come dice @MarcH, ls -al file.ext || exit $?ma se le clausole theno elsesono in qualche modo più complesse, è più gestibile.
Tripleee,

9
[[ $rc != 0 ]]ti darà un errore 0: not foundo 1: not found. Questo dovrebbe essere cambiato in [ $rc -ne 0 ]. Inoltre rc=$?potrebbe essere rimosso e appena utilizzato [ $? -ne 0 ].
CurtisLeeBolin,

223

Se vuoi lavorare con $?, Dovrai controllarlo dopo ogni comando, dato che $? viene aggiornato dopo la chiusura di ciascun comando. Ciò significa che se si esegue una pipeline, si otterrà solo il codice di uscita dell'ultimo processo nella pipeline.

Un altro approccio è quello di fare questo:

set -e
set -o pipefail

Se lo metti in cima allo script della shell, sembra che Bash se ne occuperà per te. Come notato da un precedente poster, "set -e" farà uscire bash con un errore su qualsiasi semplice comando. "set -o pipefail" causerà la chiusura di bash con un errore su qualsiasi comando in una pipeline.

Vedi qui o qui per ulteriori discussioni su questo problema. Ecco la sezione manuale di bash sul set incorporato.


6
Questa dovrebbe davvero essere la risposta migliore: è molto, molto più facile farlo che usare PIPESTATUSe controllare i codici di uscita ovunque.
candu,

2
#!/bin/bash -eè l'unico modo per avviare uno script di shell. Puoi sempre usare cose come foo || handle_error $?se hai davvero bisogno di esaminare gli stati di uscita.
Davis Herring,

53

" set -e" è probabilmente il modo più semplice per farlo. Metti questo prima di qualsiasi comando nel tuo programma.


6
@SwaroopCH lo set -escript si interromperà se un comando nel tuo script esce con stato di errore e non hai gestito questo errore.
Andrew,

2
set -eè equivalente al 100% al set -o errexitquale a differenza del primo può essere cercato. Cerca opengroup + errexit per la documentazione ufficiale.
Marc

30

Se si chiama exit in bash senza parametri, verrà restituito il codice di uscita dell'ultimo comando. In combinazione con OR, bash dovrebbe invocare exit solo se il comando precedente fallisce. Ma non l'ho provato.

comando1 || Uscita;
comando2 || Uscita;

Bash memorizzerà anche il codice di uscita dell'ultimo comando nella variabile $ ?.


25
[ $? -eq 0 ] || exit $?; # exit for none-zero return code

3
Non dovrebbe essere exit $?(niente parentesi)?
Malta,

21

http://cfaj.freeshell.org/shell/cus-faq-2.html#11

  1. Come ottengo il codice di uscita di cmd1incmd1|cmd2

    Innanzitutto, nota che il cmd1codice di uscita potrebbe essere diverso da zero e comunque non significa un errore. Questo accade ad esempio in

    cmd | head -1

    potresti osservare uno stato di uscita 141 (o 269 con ksh93) di cmd1, ma è perché è cmdstato interrotto da un segnale SIGPIPE quando head -1terminato dopo aver letto una riga.

    Conoscere lo stato di uscita degli elementi di una pipeline cmd1 | cmd2 | cmd3

    un. con zsh:

    I codici di uscita sono forniti nell'array speciale pipestatus. cmd1il codice di uscita è inserito $pipestatus[1], il cmd3codice di uscita è attivo $pipestatus[3], quindi $?è sempre lo stesso di $pipestatus[-1].

    b. con bash:

    I codici di uscita sono forniti PIPESTATUSnell'array speciale. cmd1il codice di uscita è inserito ${PIPESTATUS[0]}, il cmd3codice di uscita è attivo ${PIPESTATUS[2]}, quindi $?è sempre lo stesso di ${PIPESTATUS: -1}.

    ...

    Per maggiori dettagli consultare il seguente link .


19

per bash:

# this will trap any errors or commands with non-zero exit status
# by calling function catch_errors()
trap catch_errors ERR;

#
# ... the rest of the script goes here
#  

function catch_errors() {
   # do whatever on errors
   # 
   #
   echo "script aborted, because of errors";
   exit 0;
}

19
Probabilmente non dovrebbe "uscire da 0", poiché ciò indica il successo.
nobar,

3
exit_code = $ ?; echo "script interrotto, a causa di errori"; exit $ exit_code
RaSergiy

1
@ HAL9001 ne hai prove? La documentazione IBM dice diversamente .
Patrick James McDougle,

11

In bash questo è facile, basta collegarli con &&:

command1 && command2 && command3

Puoi anche usare il nidificato se costrutto:

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi

+1 Questa era la soluzione più semplice che stavo cercando. Inoltre, puoi anche scrivere if (! command)se ti aspetti un codice di errore diverso da zero dal comando.
Berci,

questo è per i comandi sequenziali .. cosa succede se voglio lanciare quei 3 in parallelo e uccidere tutti se qualcuno di loro fallisce?
Vasile Surdu,

4
#
#------------------------------------------------------------------------------
# run a command on failure exit with message
# doPrintHelp: doRunCmdOrExit "$cmd"
# call by:
# set -e ; doRunCmdOrExit "$cmd" ; set +e
#------------------------------------------------------------------------------
doRunCmdOrExit(){
    cmd="$@" ;

    doLog "DEBUG running cmd or exit: \"$cmd\""
    msg=$($cmd 2>&1)
    export exit_code=$?

    # if occured during the execution exit with error
    error_msg="Failed to run the command:
        \"$cmd\" with the output:
        \"$msg\" !!!"

    if [ $exit_code -ne 0 ] ; then
        doLog "ERROR $msg"
        doLog "FATAL $msg"
        doExit "$exit_code" "$error_msg"
    else
        #if no errors occured just log the message
        doLog "DEBUG : cmdoutput : \"$msg\""
        doLog "INFO  $msg"
    fi

}
#eof func doRunCmdOrExit

2
C'è raramente un motivo da usare $*; utilizzare "$@"invece per preservare spazi e caratteri jolly.
Davis Herring,
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.