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.