„Pułapka… INT TERM EXIT” naprawdę konieczne?

63

Wiele przykładów trapużycia trap ... INT TERM EXITdo zadań czyszczenia. Ale czy naprawdę trzeba wymienić wszystkie trzy sigspecy?

Instrukcja mówi:

Jeśli SIGNAL_SPEC to EXIT (0), ARG jest wykonywane przy wyjściu z powłoki.

które moim zdaniem ma zastosowanie niezależnie od tego, czy skrypt zakończył się normalnie, czy zakończył się, ponieważ otrzymał SIGINTlub SIGTERM. Eksperyment potwierdza również moje przekonanie:

$ cat ./trap-exit
#!/bin/bash
trap 'echo TRAP' EXIT
sleep 3
$ ./trap-exit & sleep 1; kill -INT %1
[1] 759
TRAP
[1]+  Interrupt               ./trap-exit
$ ./trap-exit & sleep 1; kill -TERM %1
[1] 773
TRAP
[1]+  Terminated              ./trap-exit

Dlaczego więc tak wiele przykładów wymienia wszystkie z nich INT TERM EXIT? A może coś mi umknęło i czy jest jakikolwiek przypadek, w którym EXITbrakowałaby podeszwa ?

musiphil
źródło
3
Należy również pamiętać, że przy takiej specyfikacji INT TERM EXITkod czyszczenia jest wykonywany dwukrotnie, gdy SIGTERMlub SIGINTjest odbierany.
maxschlepzig

Odpowiedzi:

19

Specyfikacja POSIX nie mówi wiele o warunkach powodujących wykonanie pułapki EXIT, a jedynie o tym, jak musi wyglądać jej środowisko podczas wykonywania.

W skorupie popiołu Busybox test wyjścia z pułapki nie echa „TRAP” przed wyjściem z powodu SIGINT lub SIGTERM. Podejrzewam, że istnieją inne pociski, które również mogą nie działać w ten sposób.

# /tmp/test.sh & sleep 1; kill -INT %1
# 
[1]+  Interrupt                  /tmp/test.sh
# 
# 
# /tmp/test.sh & sleep 1; kill -TERM %1
# 
[1]+  Terminated                 /tmp/test.sh
# 
Shawn J. Goff
źródło
3
dashrównież nie pułapkuje, EXITgdy tylko odbierze SIGINT/SIGTERM.
maxschlepzig
4
zshrównież - być może bashjest to jedyna powłoka, w której EXITrównież dopasowuje sygnały.
maxschlepzig
@maxschlepzig zshnie łapie pułapki, EXITgdy odbiera INT, ale robi to, gdy odbiera TERM. EDYCJA: Właśnie zauważyłem, ile to miało lat ...
JoL
27

Tak, jest różnica.

Ten skrypt zakończy działanie po naciśnięciu Enter, wysłaniu SIGINTlub SIGTERM:

trap '' EXIT
echo ' --- press ENTER to close --- '
read response

Ten skrypt zakończy działanie po naciśnięciu Enter:

trap '' EXIT INT TERM
echo ' --- press ENTER to close --- '
read response

* Testowane w sh , Bash i Zsh . (nie działa już w sh po dodaniu polecenia uruchomienia pułapki)


Jest też to, co powiedział @Shawn: Ash i Dash nie chwytają sygnałów EXIT.

Aby więc solidnie obsługiwać sygnały, najlepiej unikać pułapkowania EXITi użyć czegoś takiego:

cleanup() {
    echo "Cleaning stuff up..."
    exit
}

trap cleanup INT TERM
echo ' --- press ENTER to close --- '
read var
cleanup
Zaz
źródło
1
Rozwiązanie z oczyszczaniem robi dobrze - bardzo elegancko! Stało się to idiomem dla moich skryptów bash z mktempwywołaniami.
Bjoern Dahlgren
2
Czy to exitkonieczne w cleanup?
jarno
3
To nie działa, jeśli w kodzie występują błędy powłoki, które powodują przedwczesne zakończenie działania.
ijw
2
@ijw: W Bash i Ksh możesz ERRsobie z tym poradzić, ale nie jest to przenośne .
Zaz
5
To rozwiązanie nie jest niezawodne, gdy nazywa je inna powłoka. Nie obsługuje oczekiwania na wspólne wyjście ; będziesz chciał trap - INT TERM; kill -2 $$jako ostatni wiersz czyszczenia, aby powiedzieć powłoce nadrzędnej, że zakończyła przedwcześnie. Jeśli powłoka nadrzędna foobar.sh wywołuje twój skrypt (foo.sh), a następnie wywołuje bar.sh, nie chcesz, aby bar.sh wykonał się, jeśli INT / TERM zostanie wysłany do twojego foo.sh. trap cleanup EXITzajmie się tą propagacją automatycznie, więc jest to najbardziej niezawodna funkcja IMO. Oznacza to również, że nie będziesz musiał dzwonić cleanupna końcu skryptu.
Nicholas Pipitone
12

Udoskonalenie ostatniej odpowiedzi, ponieważ ma problemy:

# Our general exit handler
cleanup() {
    err=$?
    echo "Cleaning stuff up..."
    trap '' EXIT INT TERM
    exit $err 
}
sig_cleanup() {
    trap '' EXIT # some shells will call EXIT after the INT handler
    false # sets $?
    cleanup
}
trap cleanup EXIT
trap sig_cleanup INT QUIT TERM

Punkty powyżej:

Programy obsługi INT i TERM nie zamykają się dla mnie podczas testowania - obsługują błąd, a następnie powłoka powraca do wyjścia (i nie jest to zbyt zaskakujące). Zapewniam więc, że czyszczenie kończy się później, aw przypadku sygnałów zawsze używa kodu błędu (aw innym przypadku normalnego wyjścia zachowuje kod błędu).

W przypadku bash wydaje się, że wychodzenie z modułu obsługi INT wywołuje również moduł obsługi EXIT, dlatego odwijam moduł obsługi wyjścia i sam go nazywam (który będzie działał w dowolnej powłoce niezależnie od zachowania).

Wyłapuję wyjście, ponieważ skrypty powłoki mogą wyjść, zanim osiągną dno - błędy składniowe, zestaw -e i niezerowy powrót, po prostu wywołując exit. Nie możesz polegać na tym, że skrypt-skrypt dojdzie do sedna.

SIGQUIT to Ctrl- \ jeśli nigdy tego nie próbowałeś. Daje ci bonusowy rdzeń. Więc myślę, że warto również złapać w pułapkę, nawet jeśli jest to trochę niejasne.

Doświadczenie z przeszłości mówi, że jeśli (podobnie jak ja) zawsze naciskasz Ctrl-C kilka razy, czasami złapiesz go w połowie procedury czyszczenia skryptu powłoki, więc to działa, ale nie zawsze tak idealnie, jak chcesz.

ijw
źródło
2
Rozmówca będzie tylko dostać 1 jako kod wyjścia, bez względu na to, co spowodowało wyjście sygnału, natomiast withour traprozmówcy dostanie 130 dla SIGINT, 143 dla SIGTERM itp Więc chciałbym uchwycić i przekazać poprawny kod wyjścia jak: sig_cleanup() { err=$?; trap '' EXIT; (exit $err); cleanup; }.
musiphil
2
Czy możesz wyjaśnić cel trap '' EXIT INT TERMfunkcji czyszczenia? Czy ma to zapobiec przypadkowemu zakłóceniu czyszczenia przez użytkownika, o którym wspomniałeś w ostatnim akapicie? Czy to nie jest EXITzbędne?
Sześć