Jak wyjść, jeśli polecenie nie powiodło się?

222

Jestem notobem w skryptowaniu powłoki. Chcę wydrukować wiadomość i zakończyć skrypt, jeśli polecenie się nie powiedzie. Próbowałem:

my_command && (echo 'my_command failed; exit)

ale to nie działa. Nadal wykonuje instrukcje następujące po tym wierszu w skrypcie. Używam Ubuntu i bash.

użytkownik459246
źródło
5
Czy zamierzałeś, aby niezamknięty cytat był błędem składni, który spowodowałby błąd / wyjście? jeśli nie, zamknij ofertę w swoim przykładzie.
płyty grzejne

Odpowiedzi:

406

Próbować:

my_command || { echo 'my_command failed' ; exit 1; }

Cztery zmiany:

  • Zmień &&na||
  • Użyj { }zamiast( )
  • Przedstaw ;po exiti
  • odstępy {przed i przed}

Ponieważ chcesz wydrukować wiadomość i wyjść dopiero po wydaniu polecenia nie powiedzie się (kończy pracę z wartością niezerową), które potrzebują ||nie jest &&.

cmd1 && cmd2

uruchomi się, cmd2gdy się cmd1powiedzie (wartość wyjściowa 0). Natomiast

cmd1 || cmd2

uruchomi się, cmd2gdy cmd1zawiedzie (wartość wyjściowa niezerowa).

Użycie ( )powoduje, że polecenie wewnątrz nich działa w podpowłoce, a wywołanie exitstamtąd powoduje wyjście z podpowłoki, a nie oryginalnej powłoki, dlatego wykonywanie jest kontynuowane w oryginalnej powłoce.

Aby przezwyciężyć to użycie { }

Ostatnie dwie zmiany są wymagane przez bash.

kodaddict
źródło
4
Wygląda na to, że jest „odwrócony”. Jeśli funkcja „powiedzie się”, zwraca 0, a jeśli „zawiedzie”, zwraca wartość niezerową, dlatego && może oczekiwać, że oceni, kiedy pierwsza połowa zwróci wartość niezerową. To nie znaczy, że powyższa odpowiedź jest nieprawidłowa - nie, jest poprawna. i& i || w skryptach działa w oparciu o sukces, a nie o wartość zwracaną.
CashCow,
7
Wydaje się odwrócony, ale należy go odczytać i ma sens: „zrób to polecenie (pomyślnie)„ LUB ”wydrukuj ten błąd i wyjdź”
simpleuser
2
Logika tego polega na tym, że język używa oceny zwarcia (SCE). W przypadku SCE, f wyrażenie ma postać „p OR q”, a p jest oceniane jako prawdziwe, to nie ma powodu, aby nawet patrzeć na q. Jeśli wyrażenie ma postać „p AND q”, a p jest ocenione jako fałszywe, nie ma powodu, aby patrzeć na q. Powód tego jest dwojaki: 1) jest szybszy, z oczywistych powodów i 2) unika pewnych rodzajów błędów (na przykład: „jeśli x! = 0 i 10 / x> 5” ulegnie awarii, jeśli nie ma SCE ). Możliwość korzystania z tego w wierszu polecenia jest szczęśliwym efektem ubocznym.
user2635263,
2
Polecam echo STDERR:{ (>&2 echo 'my_command failed') ; exit 1; }
rynop
127

Inne odpowiedzi dobrze ujęły bezpośrednie pytanie, ale możesz również być zainteresowany set -e. Dzięki temu każde polecenie, które się nie powiedzie (poza określonymi kontekstami, takimi jak iftesty), spowoduje przerwanie skryptu. W przypadku niektórych skryptów jest to bardzo przydatne.

Daenyth
źródło
3
Niestety jest to również bardzo niebezpieczne - set -ema długi i tajemniczy zestaw zasad dotyczących tego, kiedy działa, a kiedy nie. (Jeśli ktoś uruchomi się if yourfunction; then ..., set -enigdy nie uruchomi się w środku yourfunctionani nic, co wywoła , ponieważ kod ten jest uważany za „zaznaczony”). Zobacz sekcję ćwiczeń BashFAQ # 105 , omawiając tę ​​i inne pułapki (niektóre z nich dotyczą tylko określonych wersji powłoki, więc musisz koniecznie przetestować każdą wersję, która może być użyta do uruchomienia twojego skryptu).
Charles Duffy,
67

Zauważ też, że status wyjścia każdego polecenia jest przechowywany w zmiennej powłoki $ ?, którą możesz sprawdzić natychmiast po uruchomieniu polecenia. Niezerowy status oznacza błąd:

my_command
if [ $? -eq 0 ]
then
    echo "it worked"
else
    echo "it failed"
fi
Alex Howansky
źródło
10
Można to po prostu zastąpić, jeśli my_command, nie ma potrzeby używania tutaj polecenia testowego.
Bart Sas,
5
+1, ponieważ uważam, że nie powinieneś zostać ukarany za wymienienie tej alternatywy - powinno być na stole - chociaż jest to trochę brzydkie i według mojego doświadczenia zbyt łatwe jest uruchomienie kolejnej komendy bez zauważenia charakteru testu (może ja ” po prostu głupi).
Tony Delroy
1
@BartSas Jeśli polecenie jest długie, lepiej umieścić je w osobnej linii. Dzięki temu skrypt jest bardziej czytelny.
Michael,
@Michael, nie jeśli tworzy zależności między liniami, musisz wiedzieć, co się stało w linii A, zanim zrozumiesz linię B (lub, co gorsza, musisz wiedzieć, co się stanie w linii B, zanim będziesz wiedział, że można bezpiecznie coś dodać nowy po końcu linii A). Widziałem zbyt wiele razy, gdy ktoś dodał echo "Just finished step A", nie wiedząc, że to echonadpisało to, $?co miało być sprawdzone później.
Charles Duffy,
67

Jeśli chcesz takie zachowanie dla wszystkich poleceń w skrypcie, po prostu dodaj

  set -e 
  set -o pipefail

na początku skryptu. Ta para opcji informuje interpretera bash, aby zakończył działanie za każdym razem, gdy polecenie powróci z niezerowym kodem wyjścia.

Nie pozwala to jednak na wydrukowanie komunikatu wyjścia.

damienfrancois
źródło
8
Możesz uruchamiać polecenia przy wyjściu za pomocą trapwbudowanego polecenia bash.
Gavin Smith
To jest odpowiedź na pytanie XY.
jwg
5
Interesujące może być wyjaśnienie, jakie polecenia wykonują niezależnie zamiast pary. Zwłaszcza, że set -o pipefailmoże nie być pożądanym zachowaniem. Wciąż dziękuję za zwrócenie na to uwagi!
Antoine Pinsard
3
set -enie jest wcale wiarygodny, spójny ani przewidywalny! Aby się o tym przekonać, przejrzyj sekcję ćwiczeń BashFAQ # 105 lub tabelę porównującą zachowania różnych muszli na stronie inulm.de/~mascheck/various/set-e
Charles Duffy
14

Zhackowałem następujący idiom:

echo "Generating from IDL..."
idlj -fclient -td java/src echo.idl
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Compiling classes..."
javac *java
if [ $? -ne 0 ]; then { echo "Failed, aborting." ; exit 1; } fi

echo "Done."

Poprzedź każde polecenie informacyjnym echem i podążaj za każdym poleceniem w tym samym
if [ $? -ne 0 ];...wierszu. (Oczywiście możesz edytować ten komunikat o błędzie, jeśli chcesz).

Grant Birchmeier
źródło
Można to zdefiniować jako funkcję, dlatego użyj go ponownie.
Eric Wang,
1
„jeśli [$? -ne 0]; to… fi ”to skomplikowany sposób pisania„ || ”
kevin cline
if ! javac *.java; then ...- po $?co w ogóle zawracać sobie głowę ?
Charles Duffy,
Charles - bo w najlepszym razie jestem przeciętnym skrypciarzem :)
Grant Birchmeier
10

Pod warunkiem, że my_commandjest kanonicznie zaprojektowany, tzn. Zwraca 0, gdy się powiedzie, to &&jest dokładnie odwrotne niż chcesz. Chcesz ||.

Zauważ też, że (nie wydaje mi się to właściwe w bash, ale nie mogę spróbować z miejsca, w którym jestem. Powiedz mi.

my_command || {
    echo 'my_command failed' ;
    exit 1; 
}
Benoit
źródło
IIRC, potrzebujesz średników po każdej linii wewnątrz {}
Alex Howansky
9
@Alex: nie, jeśli są na osobnej linii.
Wstrzymano do odwołania.
5

Możesz także użyć, jeśli chcesz zachować status błędu wyjścia i mieć czytelny plik z jednym poleceniem w wierszu:

my_command1 || exit $?
my_command2 || exit $?

Nie spowoduje to jednak wydrukowania dodatkowego komunikatu o błędzie. Ale w niektórych przypadkach błąd i tak zostanie wydrukowany przez nieudane polecenie.

alekspiryna
źródło
1
Nie ma ku temu powodu exit $?; po prostu exitużywa $?jako wartości domyślnej.
Charles Duffy,
@CharlesDuffy nie wiedział. Niesamowite, więc jest jeszcze prostsze!
alekspiryna
3

trapWbudowane Powłoka umożliwia chwytnych sygnały oraz inne korzystne warunki, w tym nieudanej realizacji poleceń (tj niezerowym w obie strony). Więc jeśli nie chcesz jawnie testować statusu powrotu każdej pojedynczej komendy, którą możesz wypowiedzieć, trap "your shell code" ERRa kod powłoki zostanie wykonany za każdym razem, gdy komenda zwróci niezerowy status. Na przykład:

trap "echo script failed; exit 1" ERR

Zauważ, że podobnie jak w innych przypadkach chwytania nieudanych poleceń, rurociągi wymagają specjalnego traktowania; powyższe nie złapie false | true.

Kozel
źródło