Beenden Sie die Exit-Codes, wenn Sie SIGINT und ähnliches einfangen?

14

Wenn ich trapwie auf http://linuxcommand.org/wss0160.php#trap beschrieben verwende , um Strg-C (oder ähnliches) abzufangen und vor dem Beenden zu bereinigen, ändere ich den zurückgegebenen Exit-Code.

Jetzt wird dies in der realen Welt wahrscheinlich keinen Unterschied machen (z. B. weil die Exit-Codes nicht portierbar sind und darüber hinaus nicht immer eindeutig sind, wie unter Standard-Exit-Code beschrieben, wenn der Prozess beendet wird? ), Aber ich frage mich immer noch, ob es welche gibt Wirklich keine Möglichkeit, dies zu verhindern und stattdessen den Standardfehlercode für unterbrochene Skripte zurückzugeben?

Beispiel (in Bash, aber meine Frage sollte nicht als Bash-spezifisch angesehen werden):

#!/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. ' _

Ausgabe:

$ ./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

(Bearbeitet, um es zu entfernen, um es POSIX-konformer zu machen.)

(Erneut bearbeitet, um es stattdessen zu einem Bash-Skript zu machen, ist meine Frage jedoch nicht Shell-spezifisch.)

Bearbeitet, um das tragbare "INT" für die Falle zugunsten des nicht tragbaren "SIGINT" zu verwenden.

Bearbeitet, um nutzlose geschweifte Klammern zu entfernen und mögliche Lösungen hinzuzufügen.

Aktualisieren:

Ich habe es jetzt gelöst, indem ich einfach mit einigen fest codierten Fehlercodes beendet und EXIT abgefangen habe. Dies kann auf bestimmten Systemen problematisch sein, da der Fehlercode möglicherweise abweicht oder der EXIT-Trap nicht möglich ist. In meinem Fall ist dies jedoch in Ordnung.

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
phk
quelle
Ihr Skript sieht etwas seltsam aus: Sie sagen read, dass Sie aus dem aktuellen Coprozess lesen sollen, und trap cmd SIGINTfunktionieren nicht, wie es der Standard vorschreibt trap cmd INT.
schily
Ach ja, unter POSIX ist es natürlich ohne das SIG-Präfix.
Phk
Ups, aber dann würde "read -p" auch nicht unterstützt, also werde ich es für bash anpassen.
Phk
@schily: Ich weiß allerdings nicht, was du mit "coprocess" meinst.
Phk
Auf der Korn Shell-Manpage heißt es, dass read -pEingaben aus dem aktuellen Coprozess gelesen werden.
schily

Antworten:

4

Sie müssen lediglich den EXIT-Handler in Ihrem Bereinigungshandler ändern . Hier ist ein Beispiel:

#!/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. '
felsig
quelle
meintest du trap cleanup INTstatt trap cleanup EXIT?
Jeff Schaller
Ich glaube ich meinte EXIT. Wenn exit schließlich aufgerufen wird, ändern wir den Trap so, dass er mit return 0 beendet wird. Ich glaube nicht, dass Signalhandler rekursiv sind.
Rocky
OK, in Ihrem Beispiel fange ich (SIG) INT überhaupt nicht ein, was eigentlich das ist, was ich möchte, um etwas aufzuräumen, selbst wenn der Benutzer mein interaktives Skript über Strg-C beendet.
Phk
@phk Ok. Ich denke, obwohl Sie die Idee haben, wie Sie den Exit erzwingen können, um 0 oder einen anderen Wert zurückzugeben, war das, was ich gesammelt habe, das Problem. Ich gehe davon aus, dass Sie den Code anpassen können, um die Trait-EXIT-Einstellung in Ihrem echten Bereinigungshandler zu platzieren, der von SIGINT aufgerufen wird.
Rocky
@rocky: Mein Ziel war es, einen Trap-Handler zu haben, ohne den Exit-Code zu ändern, den ich normalerweise ohne den Handler hätte. Jetzt könnte ich einfach den Exit-Code zurückgeben, den Sie normalerweise erhalten würden, aber das Problem war, dass ich den genauen Exit-Code in diesen Fällen nicht kenne (wie in dem Thread, auf den ich verlinkt habe, und dem Beitrag von meuh zu sehen, ist es ziemlich kompliziert). Ihr Beitrag war immer noch hilfreich. Ich habe nicht daran gedacht, den Trap-Handler dynamisch neu zu definieren.
Phk
9

Tatsächlich readscheint das Unterbrechen des internen Bashs etwas anders zu sein als das Unterbrechen eines von Bash ausgeführten Befehls. Normalerweise, wenn Sie eingeben trap, $?eingestellt ist und Sie können es und fahren mit dem gleichen Wert erhalten:

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

Wenn Ihr Skript beim Ausführen eines Befehls wie sleep oder sogar eines eingebauten wie unterbrochen wird wait, werden Sie sehen

130 SIGINT
130 EXIT

und der Exit-Code ist 130. Allerdings read -pscheint $?es 0 zu sein (in meiner Version von Bash 4.3.42 sowieso).


Der Umgang mit Signalen während readkann gemäß der Änderungsdatei in meiner Version in Arbeit sein ... (/ usr / share / doc / bash / CHANGES)

Änderungen zwischen dieser Version, bash-4.3-alpha, und der vorherigen Version, bash-4.2-release.

  1. Neue Funktionen in Bash

    r. Im Posix-Modus wird "Lesen" durch ein eingeklemmtes Signal unterbrochen. Nach dem Ausführen des Trap-Handlers gibt read 128 + Signal zurück und wirft alle teilweise gelesenen Eingaben weg.

meuh
quelle
OK, das ist wirklich seltsam. Es ist 130 in der interaktiven Shell in Bash 4.3.42 (unter Cygwin), 0 in derselben Shell, wenn es sich in einem Skript- und POSIX-Modus befindet oder nicht. Aber dann unter Dash und Busybox ist es immer 1.
Phk
Ich habe den POSIX-Modus mit einem Trap ausprobiert, der nicht beendet wird, und der wird neu readgestartet, wie in der CHANGES-Datei angegeben (zu meiner Antwort hinzugefügt). Es könnte also in Arbeit sein.
Meuh
Der Exit-Code 130 ist zu 100% nicht portierbar. Während die Bourne Shell 128 + signoals Exit-Code für Signale verwendet, verwendet ksh93 256 + signo. POSIX sagt: etwas über 128 ....
schily
@schily True, wie in dem Thread erwähnt, auf den ich im ursprünglichen Beitrag verlinkt habe ( unix.stackexchange.com/questions/99112 ).
Phk
6

Jeder übliche Signal-Exit-Code ist $?beim Eintritt in den Trap-Handler verfügbar :

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

Wenn es einen separaten EXIT-Trap gibt, können Sie dieselbe Methode verwenden: Behalten Sie sofort den vom Signalhandler (falls vorhanden) bereinigten Exit-Status bei und geben Sie den gespeicherten Exit-Status zurück.

Tom Hale
quelle
Dies gilt für die meisten Muscheln? Sehr schön. localist aber nicht POSIX.
Phk
1
Bearbeitet. Ich habe nicht anders getestet als {ba,z}sh. AFAIK, es ist das Beste, was man tun kann, unabhängig davon.
Tom Hale
Dies funktioniert nicht im Bindestrich ( $?ist 1, unabhängig vom empfangenen Signal).
MoonSweep
2

Nur einen Fehlercode zurückzugeben, reicht nicht aus, um einen Exit von SIGINT zu simulieren. Ich bin überrascht, dass dies bisher niemand erwähnt hat. Weiterführende Literatur: https://www.cons.org/cracauer/sigint.html

Der richtige Weg ist:

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

Dies funktioniert mit Bash, Dash und zsh. Für eine weitere Portabilität müssen Sie numerische Signalspezifikationen verwenden (andererseits erwartet zsh einen Zeichenfolgenparameter für den killBefehl ...).

Beachten Sie auch die besondere Behandlung des EXITSignals. Dies ist darauf zurückzuführen, dass einige Shells (nämlich Bash) Traps EXITfür jedes Signal ausführen (nach möglicherweise definierten Traps für dieses Signal). Das Zurücksetzen der EXITFalle verhindert dies.

Code nur beim "normalen" Beenden ausführen

Die Prüfung [ $sig = EXIT ]ermöglicht die Ausführung von Code nur beim normalen (nicht signalisierten) Beenden. Jedoch alle haben Signale Falle haben , die bei der letzten Rücksetzen der Falle auf , EXITdann; normal_exit_only_cleanupwird auch für die Signale aufgerufen, die dies nicht tun. Es wird auch durch einen Ausgang durch ausgeführt set -e. Dies kann behoben werden, indem Sie einfangen ERR(was von Dash nicht unterstützt wird) und [ $sig = ERR ]vor dem eine Prüfung hinzufügen kill.

Vereinfachte Nur-Bash-Version

Auf der anderen Seite bedeutet dieses Verhalten, dass Sie in Bash einfach tun können

trap cleanup EXIT

um nur einen Bereinigungscode auszuführen und den Exit-Status beizubehalten.

bearbeitet

  • Erläutern Sie Bashs Verhalten von "EXIT fängt alles ein"

  • Entfernen Sie das KILL-Signal, das nicht abgefangen werden kann

  • Entfernen Sie SIG-Präfixe aus den Signalnamen

  • Versuche es nicht kill -s EXIT

  • set -eBerücksichtigen Sie / ERR

philipp2100
quelle
Es gibt keine allgemeine Anforderung, dass ein Programm, das Signale abfängt, so beendet wird, dass die Tatsache erhalten bleibt, dass es ein Signal empfangen hat. Zum Beispiel kann ein Programm erklären, dass das Senden SIGINTder Weg ist, um es herunterzufahren, und es kann entscheiden, mit 0 zu beenden, wenn es ohne Fehler beendet werden konnte, oder einen anderen Fehlercode, wenn es nicht sauber heruntergefahren wurde. Ein typisches Beispiel : top. Laufen Sie top; echo $?und drücken Sie dann Strg-C. Der Status, der auf dem Bildschirm angezeigt wird, ist 0.
Louis
1
Vielen Dank für den Hinweis. Das Poster fragte jedoch ausdrücklich nach der Beibehaltung des Exit-Codes.
philipp2100