Standard-Exit-Code bei Prozessende?


54

Wenn ein Prozess mit einem handhabbaren Signal beendet wird, wie SIGINToder SIGTERMaber nicht, wie lautet der Beendigungscode des Prozesses?

Was ist mit unbehandelbaren Signalen SIGKILL?

Nach allem, was ich sagen kann, SIGINTführt das Beenden eines Prozesses wahrscheinlich zu Exit-Code 130. Aber würde das je nach Kernel- oder Shell-Implementierung variieren?

$ cat myScript
#!/bin/bash
sleep 5
$ ./myScript
<ctrl-c here>
$ echo $?
130

Ich bin mir nicht sicher, wie ich die anderen Signale testen würde ...

$ ./myScript &
$ killall myScript
$ echo $?
0  # duh, that's the exit code of killall
$ killall -9 myScript
$ echo $?
0  # same problem

1
Ihre killall myScriptArbeiten, daher ist die Rückgabe des killall (und nicht des Skripts!) 0. Sie könnten ein kill -x $$[x als Signalnummer setzen und $$ normalerweise durch die Shell auf die PID des Skripts erweitern (funktioniert in sh, bash, ...)] innerhalb des Skripts und testen Sie dann, was der Ausgangskern war.
Olivier Dulac


Kommentar zur Halbfrage: Setzen Sie myScript nicht in den Hintergrund. (weglassen &). Senden Sie das Signal von einem anderen Shell-Prozess (in einem anderen Terminal), und verwenden Sie es, $?nachdem myScript beendet wurde.
MattBianco

Antworten:


61

Prozesse können den _exit()Systemaufruf (unter Linux, siehe auch exit_group()) mit einem Integer-Argument aufrufen , um einen Exit-Code an das übergeordnete Element zu melden. Obwohl es eine ganze Zahl ist, werden nur die 8 niedrigstwertigen Bits an die Mutter verfügbar sind (Ausnahme das ist , wenn unter Verwendung von waitid()in den übergeordneten oder Handler auf SIGCHLD diesen Code abgerufen werden , wenn auch nicht auf Linux).

Das übergeordnete Element führt in der Regel ein wait()oder aus waitpid(), um den Status des untergeordneten Elements als Ganzzahl abzurufen (obwohl auch eine waitid()etwas andere Semantik verwendet werden kann).

Unter Linux und die meisten Unix - Varianten, wenn der Prozess normal beendet, die Bits 8 bis 15 der Statusnummer wird den Exit - Code enthält , wie sie weitergegeben exit(). Wenn nicht, dann enthalten die 7 niedrigstwertigen Bits (0 bis 6) die Signalnummer und Bit 7 wird gesetzt, wenn ein Kern ausgegeben wurde.

perlist $?beispielsweise enthält die Zahl ist, wie sie durch waitpid():

$ perl -e 'system q(kill $$); printf "%04x\n", $?'
000f # killed by signal 15
$ perl -e 'system q(kill -ILL $$); printf "%04x\n", $?'
0084 # killed by signal 4 and core dumped
$ perl -e 'system q(exit $((0xabc))); printf "%04x\n", $?'
bc00 # terminated normally, 0xbc the lowest 8 bits of the status

Bourne-ähnliche Shells geben auch den Exit-Status des letzten Ausführungsbefehls in ihrer eigenen $?Variablen an. Es enthält jedoch nicht direkt die Zahl, die von zurückgegeben wurde waitpid(), sondern eine Transformation darauf und unterscheidet sich zwischen den Shells.

Allen Shells ist gemeinsam, dass sie $?die niedrigsten 8 Bits des Exit-Codes (die Nummer, an die übergeben wird exit()) enthalten, wenn der Prozess normal beendet wird.

Der Unterschied besteht darin, dass der Prozess durch ein Signal beendet wird. In allen Fällen, und das ist für POSIX erforderlich, ist die Zahl größer als 128. POSIX gibt nicht an, wie hoch der Wert sein darf. In der Praxis $?enthalten die untersten 7 Bits aller mir bekannten Bourne-ähnlichen Shells die Signalnummer. Aber wo nist die Signalnummer,

  • in Asche, zsh, pdksh, schlag, die Bourne - Shell $?ist 128 + n. Was das bedeutet , ist , dass in den Schalen, wenn Sie eine bekommen $?von 129, Sie wissen nicht, ob es daran , dass der Prozess mit verlassen , exit(129)oder ob es durch das Signal getötet wurde 1( HUPauf den meisten Systemen). Das Grundprinzip ist jedoch, dass Shells, wenn sie sich selbst beenden, standardmäßig den Beendigungsstatus des zuletzt beendeten Befehls zurückgeben. Wenn Sie sicherstellen $?, dass der Wert niemals größer als 255 ist, können Sie einen konsistenten Beendigungsstatus festlegen:

    $ bash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    bash: line 1: 16720 Terminated              sh -c "kill \$\$"
    8f # 128 + 15
    $ bash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    bash: line 1: 16726 Terminated              sh -c "kill \$\$"
    8f # here that 0x8f is from a exit(143) done by bash. Though it's
       # not from a killed process, that does tell us that probably
       # something was killed by a SIGTERM
    
  • ksh93, $?Ist 256 + n. Das bedeutet, dass Sie aus einem Wert von $?zwischen einem getöteten und einem nicht getöteten Prozess unterscheiden können. Neuere Versionen von beenden sich mit demselben Signal ksh, wenn sie $?größer als 255 waren, um dem übergeordneten Element denselben Beendigungsstatus melden zu können. Das klingt zwar nach einer guten Idee, bedeutet aber, dass kshein zusätzlicher Core-Dump generiert wird (der andere wird möglicherweise überschrieben), wenn der Prozess durch ein Core-Generierungssignal beendet wurde:

    $ ksh -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    ksh: 16828: Terminated
    10f # 256 + 15
    $ ksh -c 'sh -c "kill -ILL \$\$"; exit'; printf '%x\n' "$?"
    ksh: 16816: Illegal instruction(coredump)
    Illegal instruction(coredump)
    104 # 256 + 15, ksh did indeed kill itself so as to report the same
        # exit status as sh. Older versions of `ksh93` would have returned
        # 4 instead.
    

    Man könnte sogar sagen, dass es einen Fehler gibt, ksh93der sich selbst tötet, selbst wenn $?er return 257von einer Funktion ausgeführt wird:

    $ ksh -c 'f() { return "$1"; }; f 257; exit'
    zsh: hangup     ksh -c 'f() { return "$1"; }; f 257; exit'
    # ksh kills itself with a SIGHUP so as to report a 257 exit status
    # to its parent
    
  • yash. yashbietet einen Kompromiss. Es kehrt zurück 256 + 128 + n. Das heißt, wir können auch zwischen einem abgebrochenen und einem ordnungsgemäß abgebrochenen Prozess unterscheiden. Und beim Verlassen meldet es sich, 128 + nohne sich selbst umbringen zu müssen, und welche Nebenwirkungen es haben kann.

    $ yash -c 'sh -c "kill \$\$"; printf "%x\n" "$?"'
    18f # 256 + 128 + 15
    $ yash -c 'sh -c "kill \$\$"; exit'; printf '%x\n' "$?"
    8f  # that's from a exit(143), yash was not killed
    

Um das Signal aus dem Wert von zu erhalten $?, können Sie auf tragbare Weise Folgendes verwenden kill -l:

$ /bin/kill 0
Terminated
$ kill -l "$?"
TERM

(aus Gründen der Portabilität sollten Sie niemals Signalnummern verwenden, sondern nur Signalnamen)

An den Nicht-Bourne-Fronten:

  • csh/ tcshund fishwie die Bourne-Shell, mit der Ausnahme, dass sich der Status in $statusstatt befindet $?(beachten Sie, dass dies zshauch $statusfür die Kompatibilität mit csh(zusätzlich zu $?) festgelegt ist).
  • rc: der Exit-Status ist auch in $status, aber wenn diese Variable von einem Signal getötet wird, enthält sie den Namen des Signals (wie sigtermoder sigill+corewenn ein Core generiert wurde) anstelle einer Zahl, was ein weiterer Beweis für das gute Design dieser Shell ist .
  • es. Der Exit-Status ist keine Variable. Wenn Sie sich darum kümmern, führen Sie den Befehl wie folgt aus:

    status = <={cmd}
    

    die wird eine Nummer sigtermoder sigsegv+corewie in zurückgeben rc.

Vielleicht für die Vollständigkeit, sollten wir erwähnen , zsh‚s $pipestatusund bash‘ s - $PIPESTATUSArrays , die den Exit - Status der Komponenten der letzten Pipeline enthalten.

Und auch der Vollständigkeit returnhalber , wenn es um Shell-Funktionen und Quelldateien geht, kehren Funktionen standardmäßig mit dem Exit-Status des letzten Befehlslaufs zurück, können aber mit dem eingebauten Befehl auch explizit einen Return-Status setzen. Und wir sehen hier einige Unterschiede:

  • bashund mksh(seit R41 wird anscheinend absichtlich eine Regression ^ Wchange eingeführt ) wird die Zahl (positiv oder negativ) auf 8 Bits abgeschnitten. So zum Beispiel return 1234wird festgelegt $?auf 210, return -- -1wird eingestellt $?auf 255.
  • zshund pdksh(und andere Ableitungen als mksh) erlauben jede vorzeichenbehaftete 32-Bit-Dezimalzahl (-2 31 bis 2 31 -1) (und kürzen die Zahl auf 32 Bit ).
  • ashund yasherlaube eine beliebige positive ganze Zahl von 0 bis 2 31 -1 und gebe einen Fehler für eine beliebige Zahl davon zurück.
  • ksh93Für return 0die return 320Einstellung $?wie sie ist, aber für alles andere, kürzen Sie auf 8 Bits. Beachten Sie, wie bereits erwähnt, dass sich das Zurückgeben einer Zahl zwischen 256 und 320 kshbeim Beenden selbst töten kann.
  • rcund eserlauben, alles auch Listen zurückzugeben.

Beachten Sie auch, dass einige Shells auch spezielle Werte von $?/ verwenden $status, um einige Fehlerzustände zu melden, die nicht den Beendigungsstatus eines Prozesses darstellen, z. B. 127oder 126für einen Befehl, der nicht gefunden oder nicht ausführbar ist (oder einen Syntaxfehler in einer Quelldatei) ...


1
an exit code to their parentund to get the *status* of their child. Sie haben den Schwerpunkt auf "Status" gelegt. Ist exit codeund *status*das gleiche? Fall ja, woher kommen zwei Namen? Falls nicht gleich, könnten Sie die Definition / Referenz des Status angeben?
n611x007

2
Hier gibt es 3 Zahlen. Der Exit-Code : Die Nummer, die an übergeben wurde exit(). Der Exit-Status : Die Nummer, anhand waitpid()derer der Exit-Code, die Signalnummer und die Frage, ob ein Core gelöscht wurde, ermittelt werden. Und die Zahl, die einige Shells in einer ihrer speziellen Variablen ( $?, $status) zur Verfügung stellen, ist eine solche Transformation des Exit-Status , die den Exit-Code für den Fall einer normalen Beendigung enthält, aber auch Signalinformationen enthält, wenn Der Prozess wurde abgebrochen (dieser Prozess wird im Allgemeinen auch als Exit-Status bezeichnet ). Das ist alles in meiner Antwort erklärt.
Stéphane Chazelas

1
Ich verstehe, danke! Ich schätze diesen ausdrücklichen Hinweis auf den Unterschied hier auf jeden Fall. Diese Ausdrücke in Bezug auf den Ausgang werden an einigen Stellen so synonym verwendet, dass es sich lohnt, sie zu verwenden. Hat die Shell-Variablenvariante überhaupt einen (allgemeinen) Namen? Deshalb würde ich vorschlagen, es explizit zu klären, bevor ich auf die Details der Muscheln eingehe. Ich würde vorschlagen, die Erklärung (aus Ihrem Kommentar) nach Ihrem ersten oder zweiten Absatz einzufügen.
n611x007

1
Können Sie auf das POSIX-Zitat verweisen, das besagt, dass die ersten 7 Bits das Signal sind? Alles, was ich finden konnte, war der > 128Teil: "Der Beendigungsstatus eines Befehls, der abgebrochen wurde, weil er ein Signal empfangen hat, soll als größer als 128 gemeldet werden." pubs.opengroup.org/onlinepubs/9699919799/utilities/…
Ciro Santilli

1
@cuonglm, ich glaube nicht, dass es irgendwo anders über HTTP öffentlich verfügbar ist. Sie können es immer noch über NNTP von gmane beziehen. Suchen Sie nach der Nachrichten-ID efe764d811849b34eef24bfb14106f61@austingroupbugs.net(ab dem 06.05.2015) oderXref: news.gmane.org gmane.comp.standards.posix.austin.general:10726
Stéphane Chazelas,

23

Wenn ein Prozess beendet wird, gibt er einen ganzzahligen Wert an das Betriebssystem zurück. Bei den meisten Unix-Varianten wird dieser Wert modulo 256 genommen: Alles außer den niederwertigen Bits wird ignoriert. Der Status eines untergeordneten Prozesses wird über eine 16-Bit-Ganzzahl an den übergeordneten Prozess zurückgegeben

  • Die Bits 0–6 (die 7 niederwertigen Bits) sind die Signalnummer, mit der der Prozess abgebrochen wurde, oder 0, wenn der Prozess normal beendet wurde.
  • Bit 7 wird gesetzt, wenn der Prozess durch ein Signal abgebrochen und der Kern gelöscht wurde.
  • Die Bits 8-15 sind der Beendigungscode des Prozesses, wenn der Prozess normal beendet wurde, oder 0, wenn der Prozess durch ein Signal beendet wurde.

Der Status wird vom waitSystemaufruf oder einem seiner Geschwister zurückgegeben. POSIX spezifiziert nicht die genaue Kodierung des Ausgangsstatus und der Signalnummer; es bietet nur

  • eine Möglichkeit, festzustellen, ob der Ausgangsstatus einem Signal oder einem normalen Ausgang entspricht;
  • eine Möglichkeit, auf den Beendigungscode zuzugreifen, wenn der Prozess normal beendet wurde;
  • eine Möglichkeit, auf die Signalnummer zuzugreifen, wenn der Prozess durch ein Signal beendet wurde.

Genau genommen gibt es keinen Exit- Code, wenn ein Prozess durch ein Signal beendet wird: Stattdessen gibt es einen Exit- Status .

In einem Shell-Skript wird der Beendigungsstatus eines Befehls über die spezielle Variable gemeldet $?. Diese Variable kodiert den Exit-Status mehrdeutig:

  • Wenn der Prozess normal beendet wird, $?ist dies der Beendigungsstatus.
  • Wenn der Prozess durch ein Signal beendet wurde, $?ist dies bei den meisten Systemen 128 plus der Signalnummer. POSIX $?gibt in diesem Fall nur Mandate vor, die größer als 128 sind. ksh93 fügt 256 statt 128 hinzu. Ich habe noch nie eine Unix-Variante gesehen, die der Signalnummer eine andere Konstante hinzufügt.

In einem Shell-Skript können Sie daher nicht abschließend sagen, ob ein Befehl durch ein Signal beendet oder mit einem Statuscode größer als 128 beendet wurde, außer mit ksh93. Es kommt sehr selten vor, dass Programme mit Statuscodes größer als 128 beendet werden, zum Teil, weil Programmierer dies aufgrund der $?Mehrdeutigkeit vermeiden .

SIGINT ist bei den meisten Unix-Varianten Signal 2, also $?128 + 2 = 130 für einen Prozess, der von SIGINT beendet wurde. Sie sehen 129 für SIGHUP, 137 für SIGKILL usw.


Viel besser formuliert und auf den Punkt gebracht als meins, auch wenn es im Wesentlichen die gleichen Dinge sagt. Möglicherweise möchten Sie klarstellen, dass dies $?nur für Bourne-ähnliche Muscheln gilt. Siehe auch yashfür ein anderes (aber immer noch POSIX-) Verhalten. Auch nach POSIX + XSI (Unix) kill -2 "$pid"sendet a ein SIGINT an den Prozess, aber die tatsächliche Signalnummer ist möglicherweise nicht 2, also $? wird nicht unbedingt 128 + 2 (oder 256 + 2 oder 384 + 2) sein, kill -l "$?"wird aber zurückkehren INT, weshalb ich aus Gründen der Portabilität raten würde, sich nicht auf die Nummern selbst zu beziehen.
Stéphane Chazelas

8

Das hängt von deiner Muschel ab. Auf der bash(1)Manpage, Abschnitt SHELL GRAMMAR , Unterabschnitt Simple Commands :

Der Rückgabewert eines einfachen Befehls ist [...] 128+ n, wenn der Befehl durch das Signal n beendet wird .

Da SIGINTauf Ihrem System das Signal Nummer 2 ist, ist der Rückgabewert 130, wenn es unter Bash ausgeführt wird.


1
Wie in aller Welt finden Sie das oder wissen sogar, wo Sie suchen müssen? Ich verneige mich vor deinem Genie.
Cory Klein

1
@ CoryKlein: Erfahrung, meistens. Oh, und Sie werden wahrscheinlich auch die signal(7)Manpage wollen .
Ignacio Vazquez-Abrams

cooles Zeug; Wissen Sie, ob ich zufällig Dateien in C mit diesen Konstanten aufgenommen habe? +1
Rui F Ribeiro

@CoryKlein Warum hast du dies nicht als die richtige Antwort ausgewählt?
Rui F Ribeiro

3

Es scheint der richtige Ort zu sein, zu erwähnen, dass SVr4 1989 waitid () eingeführt hat, aber bisher scheint es kein wichtiges Programm zu verwenden. waitid () erlaubt es, die vollen 32 Bits vom exit () Code abzurufen.

Vor ungefähr 2 Monaten habe ich den Wait / Job Control-Teil der Bourne Shell neu geschrieben, um waitid () anstelle von waitpid () zu verwenden. Dies wurde gemacht, um die Einschränkung zu beseitigen, die den Exit-Code mit 0xFF maskiert.

Die waitid () - Schnittstelle ist viel sauberer als frühere wait () - Implementierungen, mit Ausnahme des Aufrufs cwait () von UNOS aus dem Jahr 1980.

Vielleicht interessiert es Sie, die Manpage zu lesen unter:

http://schillix.sourceforge.net/man/man1/bosh.1.html

und überprüfen Sie den Abschnitt "Parametersubstitution" auf Seite 8.

Die neuen Variablen .sh. * Wurden für die waitid () - Schnittstelle eingeführt. Diese Schnittstelle hat für die für $? Bekannten Zahlen keine mehrdeutigen Bedeutungen mehr. und machen die Anbindung viel einfacher.

Beachten Sie, dass Sie eine POSIX-kompatible waitid () benötigen, um diese Funktion nutzen zu können. Mac OS X und Linux bieten dies derzeit nicht an, aber die waitid () wird beim Aufruf von waitpid () emuliert Nicht-POSIX-Plattform erhalten Sie immer noch nur 8 Bit vom Exit-Code.

Kurz gesagt: .sh.status ist der numerische Beendigungscode, .sh.code ist der numerische Beendigungsgrund.

Zur besseren Portabilität gibt es: .sh.codename für die Textversion des Exit-Grunds, z. B. "DUMPED" und .sh.termsig, den Singal-Namen für das Signal, das den Prozess beendet hat.

Zur besseren Verwendung gibt es zwei nicht-exit-bezogene .sh.codename-Werte: "NOEXEC" und "NOTFOUND", die verwendet werden, wenn ein Programm überhaupt nicht gestartet werden kann.

FreeBSD hat den waitid () -Kerlnel-Fehler innerhalb von 20 Stunden nach meinem Bericht behoben. Linux hat noch nicht mit der Behebung begonnen. Ich hoffe, dass 26 Jahre nach der Einführung dieser Funktion, die jetzt in POSIX verfügbar ist, alle Betriebssysteme sie bald korrekt unterstützen werden.


Eine verwandte Antwort lautet unix.stackexchange.com/a/453432/5132 .
JdeBP
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.