Przerwanie skryptu powłoki, jeśli jakieś polecenie zwróci wartość niezerową?

437

Mam skrypt powłoki Bash, który wywołuje wiele poleceń. Chciałbym, aby skrypt powłoki automatycznie wychodził z wartością zwracaną 1, jeśli którekolwiek z poleceń zwróci wartość niezerową.

Czy jest to możliwe bez wyraźnego sprawdzenia wyniku każdego polecenia?

na przykład

dosomething1
if [[ $? -ne 0 ]]; then
    exit 1
fi

dosomething2
if [[ $? -ne 0 ]]; then
    exit 1
fi
Jin Kim
źródło
8
Oprócz tego set -erównież set -u(lub set -eu). -układzie kres idiotycznemu, chowającemu błędy zachowaniu, że można uzyskać dostęp do dowolnej nieistniejącej zmiennej i uzyskać pustą wartość bez diagnozy.
Kaz

Odpowiedzi:

742

Dodaj to na początku skryptu:

set -e

Spowoduje to natychmiastowe zamknięcie powłoki, jeśli zostanie zakończone proste polecenie z niezerową wartością wyjścia. Proste polecenie to dowolne polecenie, które nie jest częścią if, podczas lub do testu, lub częścią && lub || lista.

Zobacz stronę manuala bash (1) w wewnętrznym poleceniu „set”, aby uzyskać więcej szczegółów.

Osobiście uruchamiam prawie wszystkie skrypty powłoki za pomocą „set -e”. Naprawdę denerwujące jest utrzymywanie skryptu z uporem, gdy coś zawiedzie w środku i łamie założenia dla reszty skryptu.

Ville Laurikari
źródło
36
To by działało, ale lubię używać „#! / Usr / bin / env bash”, ponieważ często uruchamiam bash w innym miejscu niż / bin. I „#! / Usr / bin / env bash -e” nie działa. Poza tym miło jest mieć miejsce do modyfikacji, aby czytać „set -xe”, gdy chcę włączyć śledzenie do debugowania.
Ville Laurikari
48
Również flagi linii shebang są ignorowane, jeśli skrypt zostanie uruchomiony jako bash script.sh.
Tom Anderson,
27
Uwaga: jeśli zadeklarujesz funkcje w skrypcie bash, funkcje będą musiały zostać ustawione na nowo w treści funkcji, jeśli chcesz rozszerzyć tę funkcjonalność.
Jin Kim
8
Ponadto, jeśli źródłowy skrypt zostanie pobrany, linia shebang będzie nieistotna.
4
@JinKim Nie wydaje się, aby tak było w przypadku bash 3.2.48. Spróbuj wykonać następujące czynności wewnątrz skryptu: set -e; tf() { false; }; tf; echo 'still here'. Egzekucja jest przerywana, nawet bez set -ewewnątrz ciała tf(). Być może chciałeś powiedzieć, że set -enie jest to dziedziczone przez podpowłoki , co jest prawdą.
mklement0
202

Aby dodać do zaakceptowanej odpowiedzi:

Pamiętaj, że set -eczasami to nie wystarczy, szczególnie jeśli masz rury.

Załóżmy na przykład, że masz ten skrypt

#!/bin/bash
set -e 
./configure  > configure.log
make

... co działa zgodnie z oczekiwaniami: błąd configureprzerywa wykonanie.

Jutro dokonujesz pozornie trywialnej zmiany:

#!/bin/bash
set -e 
./configure  | tee configure.log
make

... a teraz to nie działa. Wyjaśniono to tutaj i podano obejście (tylko Bash):

#! / bin / bash
ustaw -e 
ustaw -o pipefail

./configure | tee configure.log
robić
leonbloy
źródło
1
Dziękujemy za wyjaśnienie, jak ważne jest, pipefailaby iść razem set -o!
Malcolm,
83

Instrukcje if w twoim przykładzie są niepotrzebne. Po prostu zrób to w ten sposób:

dosomething1 || exit 1

Jeśli skorzystasz z porady Ville Laurikari i skorzystasz z niej, w set -eprzypadku niektórych poleceń może być konieczne użycie tego:

dosomething || true

|| trueUczyni rurociąg komenda ma truewartość zwracana, nawet jeśli polecenie nie działa tak The -eopcja nie zabije skrypt.

Zan Lynx
źródło
1
Lubię to. Zwłaszcza, że ​​najwyższa odpowiedź jest skoncentrowana na bashu (wcale nie jest dla mnie jasne, czy / w jakim stopniu dotyczy ona skryptów zsh). I mógłbym to sprawdzić, ale twój jest po prostu jaśniejszy, ponieważ logika.
g33kz0r
set -enie jest bash-centric - jest obsługiwany nawet w oryginalnej powłoce Bourne Shell.
Marcos Vives Del Sol
27

Jeśli masz czyszczenie, które musisz zrobić przy wyjściu, możesz również użyć „pułapki” z pseudo-sygnałem ERR. Działa to tak samo, jak pułapkowanie INT lub dowolnego innego sygnału; bash rzuca ERR, jeśli jakieś polecenie zakończy się z niezerową wartością:

# Create the trap with   
#    trap COMMAND SIGNAME [SIGNAME2 SIGNAME3...]
trap "rm -f /tmp/$MYTMPFILE; exit 1" ERR INT TERM
command1
command2
command3
# Partially turn off the trap.
trap - ERR
# Now a control-C will still cause cleanup, but
# a nonzero exit code won't:
ps aux | grep blahblahblah

Lub, szczególnie jeśli używasz „set -e”, możesz uwięzić EXIT; pułapka zostanie wykonana po wyjściu skryptu z dowolnego powodu, w tym z normalnego końca, przerwania, wyjścia spowodowanego opcją -e itp.

Fholo
źródło
12

$?Zmienna jest rzadko potrzebne. Pseudo-idiom command; if [ $? -eq 0 ]; then X; finależy zawsze zapisywać jako if command; then X; fi.

Przypadki, w których $?jest to wymagane, to konieczność sprawdzenia wielu wartości:

command
case $? in
  (0) X;;
  (1) Y;;
  (2) Z;;
esac

lub kiedy $?trzeba ponownie wykorzystać lub w inny sposób zmanipulować:

if command; then
  echo "command successful" >&2
else
  ret=$?
  echo "command failed with exit code $ret" >&2
  exit $ret
fi
Mark Edgar
źródło
3
Dlaczego „zawsze należy pisać jako”? Mam na myśli, dlaczego tak powinno być? Kiedy polecenie jest długie (pomyśl o wywołaniu GCC z tuzinem opcji), wtedy o wiele bardziej czytelne jest uruchomienie polecenia przed sprawdzeniem statusu powrotu.
ysap
Jeśli polecenie jest zbyt długie, możesz je rozerwać, nazywając je (zdefiniuj funkcję powłoki).
Mark Edgar
12

Uruchom go z -elub set -ena górze.

Zobacz także set -u.

lumpynose
źródło
34
Aby potencjalnie uratować innych, trzeba przeczytać help set: -utraktuje odniesienia do zmiennych nieustalonych jako błędy.
mklement0
1
więc to jedno, set -uczy set -enie jedno i drugie? @lumpynose
ericn
1
@eric Przeszedłem na emeryturę kilka lat temu. Mimo że uwielbiałem moją pracę, mój starzejący się mózg zapomniał o wszystkim. Zgaduję, że można użyć obu razem; złe sformułowanie z mojej strony; Powinienem był powiedzieć „i / lub”.
lumpynose
3

Wyrażenie podobne do

dosomething1 && dosomething2 && dosomething3

przerwie przetwarzanie, gdy jedno z poleceń powróci z wartością niezerową. Na przykład następujące polecenie nigdy nie wyświetli polecenia „gotowe”:

cat nosuchfile && echo "done"
echo $?
1
Gabor
źródło
2
#!/bin/bash -e

powinno wystarczyć.

Baligh Uddin
źródło
-2

po prostu wrzucając kolejne w celach informacyjnych, ponieważ pojawiło się dodatkowe pytanie do wypowiedzi Marka Edgarsa, a oto dodatkowy przykład i dotyka ogólnie tematu:

[[ `cmd` ]] && echo success_else_silence

który jest taki sam cmd || exit errcodejak ktoś pokazał.

na przykład. Chcę się upewnić, że partycja nie jest zamontowana, jeśli jest zamontowana:

[[ `mount | grep /dev/sda1` ]] && umount /dev/sda1 
Malina
źródło
5
Nie, [[ cmd`]] `to nie to samo. To fałsz, jeśli dane wyjściowe polecenia są puste, a w przeciwnym razie prawda, niezależnie od statusu wyjścia polecenia.
Gilles „SO- przestań być zły”