Co oznacza set -e w skrypcie bash?

713

Badam zawartość tego pliku preinst , który skrypt wykonuje przed rozpakowaniem pakietu z pliku archiwum Debiana (.deb).

Skrypt ma następujący kod:

#!/bin/bash
set -e
# Automatically added by dh_installinit
if [ "$1" = install ]; then
   if [ -d /usr/share/MyApplicationName ]; then
     echo "MyApplicationName is just installed"
     return 1
   fi
   rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
   rm -Rf $HOME/.local/share/file-manager/actions/*
fi
# End automatically added section

Moje pierwsze zapytanie dotyczy linii:

set -e

Myślę, że reszta skryptu jest dość prosta: sprawdza, czy menedżer pakietów Debian / Ubuntu wykonuje operację instalacji. Jeśli tak, sprawdza, czy moja aplikacja została właśnie zainstalowana w systemie. Jeśli tak, skrypt wypisuje komunikat „MyApplicationName jest właśnie zainstalowany” i kończy się ( return 1oznacza to, że kończy się na „błędzie”, prawda?).

Jeśli użytkownik prosi system pakietów Debian / Ubuntu o zainstalowanie mojego pakietu, skrypt usuwa również dwa katalogi.

Czy to prawda, czy coś mi brakuje?

AndreaNobili
źródło
42
zestaw -e
Anders Lindahl
46
powód, dla którego nie można tego znaleźć w google: -e w zapytaniu jest interpretowany jako negacja. Spróbuj wykonać następujące zapytanie: zestaw bash „-e”
Maleev
3
@twalberg Kiedy zadałem sobie to samo pytanie, patrzyłem naman set
Sedat Kilinc
4
jeśli szukasz sposobu, aby to wyłączyć, zamień myślnik na prefiks plus:set +e
Tom Saleeba
@twalberg, ale pytanie prawdziwych ludzi jest o wiele bardziej interesujące niż tylko prośba robota ;-).
vdegenne

Odpowiedzi:

797

Od help set:

  -e  Exit immediately if a command exits with a non-zero status.

Ale jest to uważane przez niektórych za złą praktykę (autorzy FAQ bash i irc freenode #bash FAQ). Zaleca się stosowanie:

trap 'do_something' ERR

uruchomić do_somethingfunkcję, gdy wystąpią błędy.

Zobacz http://mywiki.wooledge.org/BashFAQ/105

Gilles Quenot
źródło
14
Czym byłby do_something, gdybym chciał mieć taką samą semantykę jak „Wyjdź natychmiast, jeśli polecenie zakończy się ze statusem niezerowym”?
CMCDragonkai
71
trap 'exit' ERR
chepner
12
ERRPułapka nie jest dziedziczona przez funkcje powłoki, więc jeśli masz funkcje, set -o errtracelub set -Epozwoli Ci ustawić pułapkę tylko raz i stosuje się go na całym świecie.
porządku,
31
czy trap 'exit' ERRzrobić cokolwiek różni się od set -e?
Andy,
22
jeśli jest to zła praktyka, to dlaczego jest używana w pakietach Debiana ?
phuclv
98

set -ezatrzymuje wykonywanie skryptu, jeśli polecenie lub potok zawiera błąd - co jest przeciwieństwem domyślnego zachowania powłoki, które polega na ignorowaniu błędów w skryptach. Wpisz help setterminal, aby wyświetlić dokumentację tego wbudowanego polecenia.

Robin Green
źródło
44
Zatrzymuje wykonywanie tylko wtedy, gdy ostatnie polecenie w potoku zawiera błąd. Istnieje opcja specyficzna dla Bash, set -o pipefailktórej można użyć do propagowania błędów, tak aby zwracana wartość polecenia potoku była niezerowa, jeśli jedno z poprzednich poleceń zakończyło się z niezerowym statusem.
Anthony Geoghegan
2
Należy pamiętać, że -o pipefailoznacza to tylko, że status wyjścia pierwszego niezerowego (tzn. Błędnego pod -o errexitwzględem warunku ) polecenia potoku jest propagowany do końca. Pozostałe polecenia w potoku nadal działają , nawet z set -o errexit. Na przykład echo success | cat - <(echo piping); echo continues, gdzie echo successreprezentuje udane, ale polecenie omylny, będzie drukować success, pipingi continues, ale false | cat - <(echo piping); echo continues, ze falsereprezentujący komendę teraz erroring cicho, nadal będzie drukować pipingprzed wyjściem.
bb010g,
54

Zgodnie bash - zestaw Wbudowane instrukcji, jeśli -e/ errexitjest ustawiony, wychodzi powłoki natychmiast jeśli rurociąg składający się z jednego prostego polecenia , listy lub związek dowodzenia zwraca stan niezerowe.

Domyślnie stanem wyjścia potoku jest status wyjścia ostatniego polecenia w potoku, chyba że pipefailopcja jest włączona (domyślnie jest wyłączona).

Jeśli tak, to zwracany przez potok status ostatniego (najbardziej na prawo) polecenia do wyjścia ze stanem niezerowym lub zero, jeśli wszystkie polecenia zakończą się pomyślnie.

Jeśli chcesz wykonać coś przy wyjściu, spróbuj zdefiniować trap, na przykład:

trap onexit EXIT

gdzie onexitjest twoja funkcja do zrobienia czegoś przy wyjściu, na przykład poniżej, który drukuje prosty ślad stosu :

onexit(){ while caller $((n++)); do :; done; }

Istnieje podobna opcja -E/errtrace która zamiast tego pułapkowałaby na ERR, np .:

trap onerr ERR

Przykłady

Przykład statusu zerowego:

$ true; echo $?
0

Przykład niezerowego statusu:

$ false; echo $?
1

Przykłady negujące status:

$ ! false; echo $?
0
$ false || true; echo $?
0

Test z pipefailwyłączeniem:

$ bash -c 'set +o pipefail -e; true | true | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; true | true | false; echo success'; echo $?
1

Test z pipefailwłączonym:

$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1
kenorb
źródło
54

Znalazłem ten post, próbując dowiedzieć się, jaki był status wyjścia skryptu, który został przerwany z powodu set -e. Odpowiedź nie wydawała mi się oczywista; stąd ta odpowiedź. Zasadniczo set -eprzerywa wykonywanie polecenia (np. Skrypt powłoki) i zwraca kod statusu wyjścia polecenia, które się nie powiodło (tj. Wewnętrzny skrypt, a nie zewnętrzny skrypt) .

Załóżmy na przykład, że mam skrypt powłoki outer-test.sh:

#!/bin/sh
set -e
./inner-test.sh
exit 62;

Kod dla inner-test.sh:

#!/bin/sh
exit 26;

Kiedy uruchamiam outer-script.shz linii poleceń, mój skrypt zewnętrzny kończy się kodem wyjścia skryptu wewnętrznego:

$ ./outer-test.sh
$ echo $?
26
entpnerd
źródło
10

Uważam, że chodzi o to, aby dany skrypt szybko zawodził.

Aby przetestować to sam, po prostu wpisz set -epolecenie bash. Teraz spróbuj uruchomić ls. Otrzymasz listę katalogów. Teraz wpisz lsd. To polecenie nie zostało rozpoznane i zwróci kod błędu, więc monit bash zostanie zamknięty (z powodu set -e).

Teraz, aby zrozumieć to w kontekście „skryptu”, użyj tego prostego skryptu:

#!/bin/bash 
# set -e

lsd 

ls

Jeśli uruchomisz go tak, jak jest, otrzymasz listę katalogów z lsostatniego wiersza. Jeśli odkomentujesz set -ei uruchom ponownie, nie zobaczysz listy katalogów, ponieważ bash przestaje przetwarzać, gdy napotka błąd lsd.

Kallin Nagelberg
źródło
Czy ta odpowiedź dodaje wgląd lub informacje, które nie zostały jeszcze przekazane innym osobom w pytaniu?
Charles Duffy
6
Myślę, że oferuje jasne, zwięzłe wyjaśnienie funkcjonalności, której nie ma w innych odpowiedziach. Nic dodatkowego, tylko bardziej skoncentrowane niż inne odpowiedzi.
Kallin Nagelberg,
9

To stare pytanie, ale żadna z odpowiedzi tutaj nie omawia użycia set -eaka set -o errexitw skryptach obsługujących pakiety Debiana. Użycie tej opcji jest obowiązkowe w tych skryptach, zgodnie z polityką Debiana; celem jest najwyraźniej uniknięcie jakiejkolwiek możliwości wystąpienia nieobsługiwanego błędu.

W praktyce oznacza to, że musisz zrozumieć, w jakich warunkach uruchomione polecenia mogą zwrócić błąd i obsłużyć każdy z tych błędów jawnie.

Typowe gotchas to np. diff(Zwraca błąd, gdy występuje różnica) i grep(zwraca błąd, gdy nie ma zgodności). Możesz uniknąć błędów dzięki jawnej obsłudze:

diff this that ||
  echo "$0: there was a difference" >&2
grep cat food ||
  echo "$0: no cat in the food" >&2

(Zwróć też uwagę, jak staramy się zawrzeć w komunikacie nazwę bieżącego skryptu i pisać komunikaty diagnostyczne na standardowy błąd zamiast na standardowe wyjście).

Jeśli żadna jawna obsługa nie jest naprawdę konieczna ani użyteczna, jawnie nic nie rób:

diff this that || true
grep cat food || :

(Użycie :polecenia no-op powłoki jest nieco niejasne, ale dość powszechne).

Powtarzam,

something || other

jest skrótem od

if something; then
    : nothing
else
    other
fi

tzn. wyraźnie mówimy, że otherpowinien być uruchamiany tylko wtedy, gdy się somethingnie powiedzie. Długa if(i inne instrukcje kontroli przepływu w powłoce, takie jak while, until) jest również prawidłowym sposobem obsługi błędu (w rzeczywistości, gdyby tak nie było, skrypty powłoki z set -enigdy nie mogłyby zawierać instrukcji kontroli przepływu!)

A także, mówiąc wprost, w przypadku braku takiego programu obsługi, set -ecały skrypt natychmiast przestanie działać z błędem, jeśli diffznajdzie różnicę lub grepnie znajdzie dopasowania.

Z drugiej strony niektóre polecenia nie generują błędu wyjścia, gdy tego chcesz. Powszechnie problematycznymi poleceniami są find(status wyjścia nie odzwierciedla, czy pliki zostały faktycznie znalezione) i sed(status wyjścia nie ujawni, czy skrypt otrzymał jakieś dane wejściowe, czy też faktycznie wykonał jakiekolwiek polecenia). Prostą ochroną w niektórych scenariuszach jest potokowanie do polecenia, które krzyczy, jeśli nie ma wyjścia:

find things | grep .
sed -e 's/o/me/' stuff | grep ^

Należy zauważyć, że stanem wyjścia potoku jest status wyjścia ostatniego polecenia w tym potoku. Tak więc powyższe polecenia faktycznie całkowicie maskują status findi sedi mówią tylko, czy w grepkońcu się udało.

(Bash oczywiście ma set -o pipefail; ale skrypty pakietów Debiana nie mogą korzystać z funkcji Bash. Polityka zdecydowanie dyktuje użycie POSIX shdla tych skryptów, chociaż nie zawsze tak było.)

W wielu sytuacjach jest to coś, na co należy uważać przy defensywnym kodowaniu. Czasami trzeba np. Przejść przez plik tymczasowy, aby zobaczyć, czy polecenie, które wytworzyło to wyjście, zakończyło się powodzeniem, nawet jeśli idiom i wygoda w przeciwnym razie skierowałyby cię do korzystania z potoku powłoki.

potrójny
źródło
to doskonała odpowiedź. i promuje najlepsze praktyki. Miałem dokładnie ten sam problem z polecenia GREP i naprawdę nie chciałem usuwać „set -e”
Minnie
7
Script 1: without setting -e
#!/bin/bash
decho "hi"
echo "hello"
This will throw error in decho and program continuous to next line

Script 2: With setting -e
#!/bin/bash
set -e
decho "hi" 
echo "hello"
# Up to decho "hi" shell will process and program exit, it will not proceed further
Manikandan Raj
źródło