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?


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ść.


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.


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.


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 $ ?.


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

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 .


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

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

+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

2
Rzadko jest powód do korzystania $*; użyj "$@"zamiast tego, aby zachować spacje i symbole wieloznaczne.
Davis Herring
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.