Zachować kody wyjścia podczas wychwytywania SIGINT i podobnych?


14

Jeśli używam trapjak opisano np. Na http://linuxcommand.org/wss0160.php#trap, aby złapać ctrl-c (lub podobny) i oczyścić przed wyjściem, zmieniam zwrócony kod wyjścia.

Teraz prawdopodobnie nie będzie to miało znaczenia w świecie rzeczywistym (np. Ponieważ kody wyjścia nie są przenośne, a ponadto nie zawsze są jednoznaczne, jak omówiono w Domyślnym kodzie wyjścia po zakończeniu procesu? ), Ale wciąż zastanawiam się, czy istnieje naprawdę nie ma sposobu, aby temu zapobiec i zamiast tego zwrócić domyślny kod błędu dla przerwanych skryptów?

Przykład (w bash, ale moje pytanie nie powinno być uważane za specyficzne dla bash):

#!/bin/bash
trap 'echo EXIT;' EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. ' _
trap 'echo SIGINT; exit 1;' INT
read -p 'If you ctrl-c me now my return code will be 1. ' _

Wynik:

$ ./test.sh # doing ctrl-c for 1st read
If you ctrl-c me now my return code will be the default for SIGTERM.
$ echo $?
130
$ ./test.sh # doing ctrl-c for 2nd read
If you ctrl-c me now my return code will be the default for SIGTERM.
If you ctrl-c me now my return code will be 1. SIGINT 
EXIT
$ echo $?
1

(Edytowane, aby usunąć, aby było bardziej zgodne z POSIX).

(Ponownie edytowane, aby uczynić go skryptem bash, moje pytanie nie jest jednak specyficzne dla powłoki).

Zmodyfikowano, aby używać przenośnego „INT” jako pułapki na rzecz nieprzenośnego „SIGINT”.

Edytowane, aby usunąć bezużyteczne nawiasy klamrowe i dodać potencjalne rozwiązanie.

Aktualizacja:

Rozwiązałem to teraz, po prostu wychodząc z kodami błędów na stałe i zatrzymując EXIT. Może to być problematyczne w niektórych systemach, ponieważ kod błędu może się różnić lub pułapka EXIT nie jest możliwa, ale w moim przypadku jest wystarczająca.

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

Twój skrypt wygląda nieco dziwnie: każesz readczytać z bieżącego koprocesu i trap cmd SIGINTnie będzie działał, ponieważ standard mówi, że powinieneś używać trap cmd INT.
schily,

Ach tak, pod POSIX oczywiście nie ma przedrostka SIG.
phk

Ups, ale wtedy „read -p” też nie byłby obsługiwany, więc zamierzam go dostosować do bash.
phk

@schily: Nie wiem jednak, co masz na myśli przez „koproces”.
phk

Cóż, strona podręcznika Korn Shell mówi, że read -pczyta dane wejściowe z bieżącego koprocesu.
schily,

Odpowiedzi:


4

Wszystko, co musisz zrobić, to zmienić moduł obsługi EXIT wewnątrz modułu procedury czyszczenia. Oto przykład:

#!/bin/bash
cleanup() {
    echo trapped exit
    trap 'exit 0' EXIT
}
trap cleanup EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. '

miałeś na myśli trap cleanup INTzamiast trap cleanup EXIT?
Jeff Schaller

Myślę, że miałem na myśli EXIT. Kiedy w końcu zostanie wywołane exit, to jednak zrobione, zmieniamy pułapkę na exit z return 0. Nie sądzę, że procedury obsługi sygnałów są rekurencyjne.
skalista

OK, w twoim przykładzie wcale nie pułapkuję (SIG) INT, co jest właściwie tym, czego chcę, aby trochę wyczyścić, nawet jeśli użytkownik wychodzi z mojego interaktywnego skryptu przez ctrl-c.
phk

@phk Ok. Myślę, że choć masz pomysł, jak zmusić wyjście do zwrócenia 0 lub innej wartości, którą zebrałem, był problemem. Zakładam, że będziesz mógł dostosować kod, aby umieścić ustawienie WYJŚCIE pułapki w swoim prawdziwym module obsługi czyszczenia, który jest wywoływany przez SIGINT.
skalisty

@rocky: Moim celem było posiadanie modułu obsługi pułapki bez zmiany kodu wyjścia, który normalnie posiadałbym bez modułu obsługi. Teraz mógłbym po prostu zwrócić kod wyjścia, który normalnie dostaniesz, ale problem polegał na tym, że nie znam dokładnego kodu wyjścia w tych przypadkach (jak widać w wątku, z którym się łączyłem i w poście od mnie, jest to dość skomplikowane). Twój post był nadal pomocny, nie myślałem o dynamicznym definiowaniu modułu obsługi pułapek.
phk

9

W rzeczywistości, przerywanie wewnętrznego bashu readwydaje się nieco inne niż przerywanie polecenia uruchamianego przez bash. Normalnie, kiedy wchodzi trap, $?jest ustawiony i można go i wyjść z tej samej wartości zachowania:

trap 'rc=$?; echo $rc SIGINT; exit $rc' INT
trap 'rc=$?; echo $rc EXIT; exit $rc' EXIT

Jeśli skrypt zostanie przerwany podczas wykonywania polecenia podobnego sleep lub nawet wbudowanego wait, zobaczysz

130 SIGINT
130 EXIT

a kod wyjścia to 130. Jednak read -pwydaje się, że $?ma wartość 0 (i tak w mojej wersji bash 4.3.42).


Obsługa sygnałów podczas readmoże być w toku, zgodnie z plikiem zmian w mojej wersji ... (/ usr / share / doc / bash / CHANGES)

zmiany między tą wersją, bash-4.3-alpha, a poprzednią wersją, bash-4.2-release.

  1. Nowe funkcje w Bash

    r. W trybie Posix „odczyt” jest przerywany przez sygnał uwięziony. Po uruchomieniu modułu obsługi pułapki odczyt zwraca sygnał 128+ i wyrzuca wszelkie częściowo odczytane dane wejściowe.


OK, to naprawdę dziwne. Ma 130 na interaktywnej powłoce na bash 4.3.42 (pod cygwin), 0 pod tą samą powłoką, gdy jest w trybie skryptu i trybie POSIX, czy nie ma znaczenia. Ale potem pod kreską i zajęty jest zawsze 1.
phk

Próbowałem trybu POSIX z pułapką, która nie wychodzi, i uruchamia się ponownie read, jak zauważono w pliku CHANGES (dodanym do mojej odpowiedzi). Może to być praca w toku.
meuh,

Kod wyjścia 130 jest w 100% nieprzenośny. Podczas gdy Bourne Shell używa 128 + signojako kodu wyjścia sygnałów, używa ksh93 256 + signo. POSIX mówi: coś powyżej 128 ....
schily

@schily True, jak zaznaczono w wątku, do którego odsyłam w oryginalnym poście ( unix.stackexchange.com/questions/99112 ).
phk

6

Każdy zwykły kod wyjścia sygnału będzie dostępny $?po wejściu do obsługi pułapki:

sig_handler() {
    exit_status=$?  # Eg 130 for SIGINT, 128 + (2 == SIGINT)
    echo "Doing signal-specific up"
    exit "$exit_status"
}
trap sig_handler INT HUP TERM QUIT

Jeśli istnieje osobna pułapka WYJŚCIA, możesz zastosować tę samą metodologię: natychmiast zachowaj status wyjścia przekazywany z procedury obsługi sygnału (jeśli istnieje) czyszczenie, a następnie zwróć zapisany status wyjścia.


Dotyczy to większości pocisków? Bardzo dobrze. localnie jest POSIX.
phk

1
Edytowane. Nie testowałem w innych niż {ba,z}sh. AFAIK, to najlepsze, co można zrobić, niezależnie od tego.
Tom Hale

To nie działa w myślniku ( $?jest 1 niezależnie od odbieranego sygnału).
MoonSweep

2

Zwrócenie tylko kodu błędu nie wystarcza, aby zasymulować wyjście przez SIGINT. Dziwi mnie, że do tej pory nikt o tym nie wspominał. Dalsza lektura: https://www.cons.org/cracauer/sigint.html

Właściwy sposób to:

for sig in EXIT ABRT HUP INT PIPE QUIT TERM; do
    trap "cleanup;
          [ $sig  = EXIT ] && normal_exit_only_cleanup;
          [ $sig != EXIT ] && trap - $sig EXIT && kill -s $sig $$
         " $sig
done

Działa to z Bash, Dash i zsh. Aby zwiększyć przenośność, musisz użyć specyfikacji sygnału numerycznego (z drugiej strony, zsh oczekuje parametru ciągu dla killpolecenia ...)

Zwróć także uwagę na specjalne traktowanie EXITsygnału. Wynika to z tego, że niektóre powłoki (mianowicie Bash) również wykonują pułapki EXITdla dowolnego sygnału (po ewentualnie zdefiniowanych pułapkach na tym sygnale). Zresetowanie EXITpułapki zapobiega temu.

Wykonywanie kodu tylko przy „normalnym” wyjściu

Sprawdzenie [ $sig = EXIT ]pozwala na wykonanie kodu tylko przy normalnym (nie sygnalizowanym) wyjściu. Jednak wszystkie sygnały muszą mieć pułapki, które w końcu resetują pułapkę EXIT; normal_exit_only_cleanupbędą również wzywane do sygnałów, które tego nie robią. Zostanie również wykonane przez wyjście przez set -e. Można to naprawić poprzez zalewkowanie ERR(które nie jest obsługiwane przez Dash) i dodanie zaznaczenia [ $sig = ERR ]przed kill.

Uproszczona wersja tylko Bash

Z drugiej strony takie zachowanie oznacza, że ​​w Bash możesz po prostu to zrobić

trap cleanup EXIT

po prostu wykonać kod czyszczenia i zachować status wyjścia.

edytowane

  • Opracuj zachowanie Basha: „WYJŚCIE łapie wszystko w pułapkę”

  • Usuń sygnał KILL, który nie może zostać uwięziony

  • Usuń prefiksy SIG z nazw sygnałów

  • Nie próbuj kill -s EXIT

  • Weź set -epod uwagę / ERR


Nie ma ogólnego wymogu, aby program przechwytujący sygnały kończył się w sposób, który zachowuje fakt, że otrzymał sygnał. Na przykład program może zadeklarować, że wysyłanie SIGINTjest sposobem na jego zamknięcie i może zdecydować o wyjściu z 0, jeśli udało mu się zakończyć bez błędu lub innym kodem błędu, jeśli nie zamknął się czysto. Sprawa w punkcie: top. Uruchom, top; echo $?a następnie naciśnij Ctrl-C. Status zrzucony na ekran będzie wynosił 0.
Louis

1
Dziękuję za zwrócenie na to uwagi. Jednak plakat konkretnie zapytał o zachowanie kodu wyjścia.
philipp2100,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.