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?
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:
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.
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.
@ 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 $ ?.
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 .
# 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;}
+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
$?
po każdym poleceniu. Prosta metoda: umieśćset -e
lub#!/bin/bash -e
na górze skryptu Bash.Odpowiedzi:
Po każdej komendzie kod wyjścia można znaleźć w
$?
zmiennej, aby uzyskać coś takiego:Trzeba uważać na polecenia potokowe, ponieważ
$?
jedyne daje kod powrotu ostatniego elementu w potoku, więc w kodzie:nie zwróci kodu błędu, jeśli plik nie istnieje (ponieważ
sed
część potoku faktycznie działa, zwracając 0).bash
Powłoka faktycznie zapewnia tablicę, która może pomóc w tej sprawie, że byciePIPESTATUS
. 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]}
:Zauważ, że otrzymujesz wynik
false
polecenia, a nie cały potok. Możesz także przetworzyć całą listę według własnego uznania:Jeśli chcesz uzyskać największy kod błędu z potoku, możesz użyć czegoś takiego:
Przechodzi kolejno przez każdy z
PIPESTATUS
elementów, przechowując go,rc
jeśli był większy niż poprzedniarc
wartość.źródło
ls -al file.ext || exit $?
([[]] nie jest przenośny)[[ ]]
jest dość przenośnybash
, na co pytanie jest oznaczone :-) O dziwo,ls
nie działa,command.com
więc też nie jest przenośny, wiem, ale jest to ten sam argument teraźniejszość.PIPESTATUS
(tj.${PIPESTATUS[0]}
Dla pierwszego polecenia,${PIPESTATUS[1]}
dla drugiego lub${PIPESTATUS[*]}
dla listy wszystkich statystyk wyjścia.$?
bezpośredniego badania . Zwykle potrzebujesz czegoś,if ls -al file.ext; then : nothing; else exit $?; fi
co oczywiście, jak twierdzi @MarcH, jest równoważne,ls -al file.ext || exit $?
ale jeśli klauzulethen
lubelse
są nieco bardziej złożone, łatwiej jest je utrzymać.[[ $rc != 0 ]]
da ci błąd0: not found
lub1: not found
błą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 ]
.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ć:
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.
źródło
PIPESTATUS
i sprawdzać kody wyjścia wszędzie.#!/bin/bash -e
to jedyny sposób na uruchomienie skryptu powłoki. Zawsze możesz użyć takich rzeczy, jakfoo || handle_error $?
jeśli chcesz faktycznie sprawdzić status wyjścia.„
set -e
” jest prawdopodobnie najłatwiejszym sposobem na zrobienie tego. Po prostu umieść to przed dowolnymi poleceniami w swoim programie.źródło
set -e
Twó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.set -e
jest w 100% równoważny temu, wset -o errexit
przeciwieństwie do pierwszego z nich, można wyszukiwać. Wyszukaj opengroup + errexit w celu uzyskania oficjalnej dokumentacji.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.
Bash zapisze również kod wyjścia ostatniego polecenia w zmiennej $ ?.
źródło
źródło
exit $?
(bez parens)?http://cfaj.freeshell.org/shell/cus-faq-2.html#11
Jak uzyskać kod zakończenia
cmd1
wcmd1|cmd2
Po pierwsze, zauważ, że
cmd1
kod wyjścia może być niezerowy i nadal nie oznacza błędu. Dzieje się tak na przykład wmożesz zaobserwować status wyjścia 141 (lub 269 z ksh93)
cmd1
, ale dzieje się tak, ponieważcmd
został przerwany przez sygnał SIGPIPE pohead -1
zakoń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.
cmd1
kod wyjścia jest$pipestatus[1]
,cmd3
kod wyjścia w$pipestatus[3]
tak, że$?
zawsze jest taka sama jak$pipestatus[-1]
.b. z uderzeniem:
Kody wyjścia znajdują się w
PIPESTATUS
specjalnej tablicy.cmd1
kod wyjścia jest${PIPESTATUS[0]}
,cmd3
kod wyjścia w${PIPESTATUS[2]}
tak, że$?
zawsze jest taka sama jak${PIPESTATUS: -1}
....
Aby uzyskać więcej informacji, zobacz poniższy link .
źródło
za bash:
źródło
W bash jest to łatwe, po prostu połącz je razem z &&:
Możesz także użyć zagnieżdżonego, jeśli konstrukcja:
źródło
if (! command)
jeśli oczekujesz niezerowego kodu błędu z polecenia.źródło
$*
; użyj"$@"
zamiast tego, aby zachować spacje i symbole wieloznaczne.