Wyjdź ze skryptu powłoki na podstawie kodu zakończenia procesu

378

Mam skrypt powłoki, który wykonuje wiele poleceń. Jak sprawić, by skrypt powłoki zakończył działanie, jeśli którakolwiek z komend zakończy działanie z niezerowym kodem wyjścia?

Mark Roddy
źródło
3
Odpowiedziałem, zakładając, że używasz bash, ale jeśli jest to zupełnie inna powłoka, czy możesz podać w swoim poście?
Martin W
Metoda trudna: przetestuj wartość $?po każdym poleceniu. Prosta metoda: umieść set -elub #!/bin/bash -ena górze skryptu Bash.
mwfearnley

Odpowiedzi:

494

Po każdej komendzie kod wyjścia można znaleźć w $?zmiennej, aby uzyskać coś takiego:

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

Trzeba uważać na polecenia potokowe, ponieważ $?jedyne daje kod powrotu ostatniego elementu w potoku, więc w kodzie:

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

nie zwróci kodu błędu, jeśli plik nie istnieje (ponieważ sedczęść potoku faktycznie działa, zwracając 0).

bashPowłoka faktycznie zapewnia tablicę, która może pomóc w tej sprawie, że bycie PIPESTATUS. Ta tablica ma jeden element dla każdego ze składników potoku, do którego można uzyskać dostęp indywidualnie, na przykład ${PIPESTATUS[0]}:

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

Zauważ, że otrzymujesz wynik falsepolecenia, a nie cały potok. Możesz także przetworzyć całą listę według własnego uznania:

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

Jeśli chcesz uzyskać największy kod błędu z potoku, możesz użyć czegoś takiego:

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

Przechodzi kolejno przez każdy z PIPESTATUSelementów, przechowując go, rcjeśli był większy niż poprzednia rcwartość.

paxdiablo
źródło
39
Ta sama funkcja w jednym wierszu przenośnego kodu: ls -al file.ext || exit $?([[]] nie jest przenośny)
MarcH
19
MarcH, myślę, że przekonasz się, że [[ ]]jest dość przenośny bash, na co pytanie jest oznaczone :-) O dziwo, lsnie działa, command.comwięc też nie jest przenośny, wiem, ale jest to ten sam argument teraźniejszość.
paxdiablo,
39
Wiem, że jest to starożytne, ale należy zauważyć, że można uzyskać kod wyjścia poleceń w potoku za pośrednictwem tablicy PIPESTATUS(tj. ${PIPESTATUS[0]}Dla pierwszego polecenia, ${PIPESTATUS[1]}dla drugiego lub ${PIPESTATUS[*]}dla listy wszystkich statystyk wyjścia.
DevSolar
11
Należy podkreślić, że eleganckie i idiomatyczne skrypty powłoki bardzo rzadko wymagają $?bezpośredniego badania . Zwykle potrzebujesz czegoś, if ls -al file.ext; then : nothing; else exit $?; fico oczywiście, jak twierdzi @MarcH, jest równoważne, ls -al file.ext || exit $?ale jeśli klauzule thenlub elsesą nieco bardziej złożone, łatwiej jest je utrzymać.
tripleee
9
[[ $rc != 0 ]]da ci błąd 0: not foundlub 1: not foundbłąd. Należy to zmienić na [ $rc -ne 0 ]. Również rc=$?może następnie zostać usunięty i tylko używane [ $? -ne 0 ].
CurtisLeeBolin
223

Jeśli chcesz pracować z $ ?, musisz to sprawdzić po każdym poleceniu, ponieważ $? jest aktualizowany po wyjściu każdego polecenia. Oznacza to, że jeśli wykonasz potok, otrzymasz tylko kod zakończenia ostatniego procesu w potoku.

Inne podejście to zrobić:

set -e
set -o pipefail

Jeśli umieścisz to na górze skryptu powłoki, wygląda na to, że bash zajmie się tym za Ciebie. Jak zauważono w poprzednim plakacie, „set -e” spowoduje, że bash zakończy działanie z błędem dowolnego prostego polecenia. „set -o pipefail” spowoduje, że bash zakończy działanie z błędem dowolnego polecenia w potoku.

Zobacz tutaj lub tutaj, aby uzyskać nieco więcej dyskusji na temat tego problemu. Oto sekcja manualna bash na wbudowanym zestawie.

Jeff Hill
źródło
6
To naprawdę powinna być najlepsza odpowiedź: jest to o wiele, znacznie łatwiej to zrobić, niż używać PIPESTATUSi sprawdzać kody wyjścia wszędzie.
candu
2
#!/bin/bash -eto jedyny sposób na uruchomienie skryptu powłoki. Zawsze możesz użyć takich rzeczy, jak foo || handle_error $?jeśli chcesz faktycznie sprawdzić status wyjścia.
Davis Herring
53

set -e” jest prawdopodobnie najłatwiejszym sposobem na zrobienie tego. Po prostu umieść to przed dowolnymi poleceniami w swoim programie.

Allen
źródło
6
@ SwaroopCH set -eTwój skrypt przerwie działanie, jeśli dowolne polecenie w skrypcie zakończy działanie ze statusem błędu i nie obsłużyłeś tego błędu.
Andrew
2
set -ejest w 100% równoważny temu, w set -o errexitprzeciwieństwie do pierwszego z nich, można wyszukiwać. Wyszukaj opengroup + errexit w celu uzyskania oficjalnej dokumentacji.
MarcH
30

Jeśli po prostu wywołasz exit w bash bez parametrów, zwróci kod wyjścia ostatniego polecenia. W połączeniu z LUB bash powinien wywoływać wyjście, tylko jeśli poprzednie polecenie się nie powiedzie. Ale nie przetestowałem tego.

polecenie 1 || wyjście;
polecenie 2 || wyjście;

Bash zapisze również kod wyjścia ostatniego polecenia w zmiennej $ ?.

Arvodan
źródło
25
[ $? -eq 0 ] || exit $?; # exit for none-zero return code
chemila
źródło
3
Czy nie powinno to być exit $?(bez parens)?
malthe
21

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

  1. Jak uzyskać kod zakończenia cmd1wcmd1|cmd2

    Po pierwsze, zauważ, że cmd1kod wyjścia może być niezerowy i nadal nie oznacza błędu. Dzieje się tak na przykład w

    cmd | head -1

    możesz zaobserwować status wyjścia 141 (lub 269 z ksh93) cmd1, ale dzieje się tak, ponieważ cmdzostał przerwany przez sygnał SIGPIPE po head -1zakończeniu po przeczytaniu jednej linii.

    Aby poznać status wyjścia elementów potoku cmd1 | cmd2 | cmd3

    za. z zsh:

    Kody wyjścia znajdują się w specjalnej tablicy pipestatus. cmd1kod wyjścia jest $pipestatus[1], cmd3kod wyjścia w $pipestatus[3]tak, że $?zawsze jest taka sama jak $pipestatus[-1].

    b. z uderzeniem:

    Kody wyjścia znajdują się w PIPESTATUSspecjalnej tablicy. cmd1kod wyjścia jest ${PIPESTATUS[0]}, cmd3kod wyjścia w ${PIPESTATUS[2]}tak, że $?zawsze jest taka sama jak ${PIPESTATUS: -1}.

    ...

    Aby uzyskać więcej informacji, zobacz poniższy link .

gatoatigrado
źródło
19

za 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;
}

źródło
19
Prawdopodobnie nie powinno „wyjść 0”, ponieważ oznacza to sukces.
nobar
3
kod_wyjścia = $ ?; echo „skrypt został przerwany z powodu błędów”; kod $ kod_wyjścia
RaSergiy
1
@ HAL9001 czy masz na to dowody? Dokumentacja IBM mówi inaczej .
Patrick James McDougle,
11

W bash jest to łatwe, po prostu połącz je razem z &&:

command1 && command2 && command3

Możesz także użyć zagnieżdżonego, jeśli konstrukcja:

if command1
   then
       if command2
           then
               do_something
           else
               exit
       fi
   else
       exit
fi
Martin W.
źródło
+1 To było najprostsze rozwiązanie, którego szukałem. Ponadto możesz także pisać, if (! command)jeśli oczekujesz niezerowego kodu błędu z polecenia.
Berci
to jest dla sekwencyjnych poleceń .. co jeśli chcę uruchomić te 3 równolegle i zabić wszystkich, jeśli któryś z nich zawiedzie?
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
Yordan Georgiev
źródło
2
Rzadko jest powód do korzystania $*; użyj "$@"zamiast tego, aby zachować spacje i symbole wieloznaczne.
Davis Herring