Verwenden von "reservierten" Codes für den Beendigungsstatus von Shell-Skripten

15

Ich bin kürzlich auf diese Liste von Exit-Codes mit besonderer Bedeutung aus dem Advanced Bash-Scripting Guide gestoßen. Sie bezeichnen diese Codes als reserviert und empfehlen Folgendes:

Gemäß der obigen Tabelle haben die Exit-Codes 1-2, 126-165 und 255 eine spezielle Bedeutung und sollten daher für benutzerdefinierte Exit-Parameter vermieden werden.

Vor einiger Zeit habe ich ein Skript geschrieben, das die folgenden Beendigungsstatuscodes verwendet:

  • 0 - Erfolg
  • 1 - falscher Hostname
  • 2 - ungültige Argumente angegeben
  • 3 - unzureichende Benutzerrechte

Als ich das Skript schrieb, waren mir keine speziellen Exit-Codes bekannt, und ich begann einfach bei 1 für die erste Fehlerbedingung und erhöhte den Exit-Status für jeden nachfolgenden Fehlertyp.

Ich habe das Skript mit der Absicht geschrieben, dass es zu einem späteren Zeitpunkt von anderen Skripten aufgerufen werden kann (die nach Exit-Codes ungleich Null suchen können). Das habe ich noch nicht gemacht. Bisher habe ich das Skript nur über meine interaktive Shell (Bash) ausgeführt und mich gefragt, was bzw. ob Probleme durch die Verwendung meiner benutzerdefinierten Beendigungscodes verursacht werden könnten. Wie relevant / wichtig ist die Empfehlung aus dem Advanced Bash-Scripting Guide?

Ich konnte keine bestätigenden Hinweise in der Bash-Dokumentation finden. Der Abschnitt über den Beendigungsstatus listet lediglich die von Bash verwendeten Beendigungscodes auf, gibt jedoch nicht an, dass diese reserviert sind , oder warnt vor deren Verwendung für Ihre eigenen Skripte / Programme.

Anthony G - Gerechtigkeit für Monica
quelle
6
Ich und andere halten das ABSG im Allgemeinen für von geringer Qualität. Meiner Meinung nach macht der Autor der von Ihnen verlinkten Seite eine nicht unterstützte Aussage, dass die aufgelisteten Exit-Codes reserviert sind, anscheinend basierend auf der Tatsache, dass die Shell selbst sie für bestimmte Bedeutungen verwendet. Es wurden Versuche unternommen, Standards für Skripte zu erstellen, von denen keines erfolgreich war. Das Wichtigste ist, die von Ihnen ausgewählten Fehlercodes zu dokumentieren, damit die Benutzer Ihrer Skripte (z. B. anderer Skripte) wissen, was sie basierend auf ihnen tun sollen.
Bis auf weiteres angehalten.
@DennisWilliamson Wenn Sie Ihren Kommentar als Antwort veröffentlichen, würde ich ihn gerne unterstützen. Ich habe bereits für alle anderen Antworten gestimmt, da ich fand, dass jede von ihnen nützlich ist. Während Ihre Antwort inhaltlich der von David King (und in geringerem Maße der von Zwol) ähnlich ist, geben Sie ausdrücklich an, dass es keine Beweise für die Behauptung im ABSG-Zitat gibt.
Anthony G - Gerechtigkeit für Monica
1
Vielen Dank für das Angebot, aber ich glaube, mein Kommentar sollte so bleiben.
Bis auf weiteres angehalten.
Inzwischen habe ich festgestellt, dass die POSIX-Spezifikation ähnliche Hinweise enthält. Daher habe ich diese Informationen zu meiner eigenen Antwort hinzugefügt (die die Ergebnisse meiner Forschung seit dem Stellen dieser Frage enthält).
Anthony G - Gerechtigkeit für Monica

Antworten:

10

Es wurden mehrere Versuche unternommen, die Bedeutung von Prozess-Exit-Codes zu standardisieren. Zusätzlich zu dem, den Sie erwähnen, weiß ich von:

  • Die BSDs haben sysexits.hBedeutungen für Werte ab 64.

  • GNU- grepDokumente, deren Beendigungscode 0 bedeutet, dass mindestens eine Übereinstimmung gefunden wurde, 1 bedeutet, dass keine Übereinstimmung gefunden wurde, und 2 bedeutet, dass ein E / A-Fehler aufgetreten ist; Diese Konvention ist natürlich auch für andere Programme nützlich, bei denen die Unterscheidung zwischen "nichts ist schiefgegangen, aber ich habe nichts gefunden" und "ein E / A-Fehler ist aufgetreten" sinnvoll ist.

  • Viele Implementierungen der C-Bibliotheksfunktion systemverwenden den Exit-Code 127, um anzuzeigen, dass das Programm nicht vorhanden ist oder nicht gestartet werden konnte.

  • Unter Windows können NTSTATUSCodes (die ungünstigerweise über den gesamten 32-Bit-Nummernraum verteilt sind) als Exit-Codes verwendet werden, insbesondere solche, die darauf hinweisen, dass ein Prozess aufgrund eines katastrophalen Fehlverhaltens abgebrochen wurde (z STATUS_STACK_OVERFLOW. B. ).

Sie können sich nicht darauf verlassen, dass ein bestimmtes Programm eine dieser Konventionen einhält. Die einzig verlässliche Regel ist, dass der Exit-Code 0 erfolgreich ist und alles andere ein Fehler ist. (Beachten Sie, dass C89 der EXIT_SUCCESSwird nicht garantiert den Wert Null haben, aber exit(0)ist erforderlich , identisch zu verhalten exit(EXIT_SUCCESS). Auch wenn die Werte nicht gleich sind )

zwol
quelle
Vielen Dank. Es war schwierig, eine Antwort vor den anderen zu wählen, aber ich akzeptiere diese, da sie meine Frage beantwortet und gleichzeitig einen umfassenden Überblick über die verschiedenen verwendeten Exit-Codes (mit relevanten Links) bietet: Sie verdient mehr als die 3 Upvotes hat derzeit.
Anthony G - Gerechtigkeit für Monica
11

Kein Exit-Code hat eine besondere Bedeutung, aber der Wert in $?kann eine besondere Bedeutung haben.

Die Art und Weise, wie Bourne Shell und ksh93 Exit-Codes und Fehlersituationen an die Shell-Variable handhabten und weiterleiteten, $?ist das Problem. Im Gegensatz zu Ihrer Auflistung haben nur die folgenden Werte für $?eine besondere Bedeutung:

  • 126 Die Binärdatei konnte nicht ausgeführt werden, obwohl sie vorhanden ist
  • 127 Die angegebene Binärdatei existiert nicht
  • 128 Exit-Status war == 0, es besteht jedoch ein nicht angegebenes Problem

Zusätzlich gibt es einen nicht spezifizierten shell- und plattformspezifischen $?Codebereich> 128, der für ein Programm reserviert ist, das durch ein Signal unterbrochen wurde:

  • Bourne Shell bash und ksh88 verwenden 128 + Signalnummer
  • ksh93 verwendet 256 + Signalnummer.

Andere Werte geben keine Probleme, da sie von den Shell-Spezialwerten unterschieden werden $?können.

Insbesondere werden die Werte 1 und 2 nicht für spezielle Bedingungen verwendet, sondern sind lediglich Exit-Codes, die von integrierten Befehlen verwendet werden, die dasselbe Verhalten zeigen können, wenn sie keine integrierten Befehle sind. Es scheint also, dass der Zeiger auf die von Ihnen bereitgestellte Bash-Skriptanleitung kein gutes Handbuch ist, da er nur die von Bash verwendeten Codes auflistet, ohne zu kommentieren, ob ein bestimmter Code ein besonderer Wert ist, der für eigene Skripte vermieden werden sollte.

Neuere Versionen der Bourne - Shell verwenden , waitid()statt waitpid()für das Programm zu beenden , zu warten und waitid()(1989 für SVr4 eingeführt) verwendet eine bessere syscall - Schnittstelle (ähnlich dem , was UNOS bereits im Jahr 1980 verwendet).

Da neuere Bourne Shell-Versionen den Exit-Grund in einer separaten Variablen ${.sh.code}/ ${.sh.codename}als den Exit-Code in ${.sh.status}/ codieren ${.sh.termsig}(siehe http://schillix.sourceforge.net/man/man1/bosh.1.html) , wird der Exit-Code nicht überladen mit speziellen Zuständen und als Ergebnis der Verwendung von `waitid () unterstützt die Bourne-Shell jetzt die Rückgabe aller 32 Bits des Exit-Codes - nicht nur der niedrigen 8 Bits.

Übrigens: Achten Sie darauf, dass Sie kein exit(256)C-Programm oder Shell-Skript verwenden, da dies dazu führt, $?dass es in einer klassischen Shell als 0 interpretiert wird.

schily
quelle
2
Übrigens: Ich habe gegen waitid()Ende Mai einen Fehlerbericht gegen FreeBSD und den Linux-Kernel für diesen Fehler erstellt. Die FreeBSD-Leute haben das Problem innerhalb von 20 Stunden behoben, die Linux-Leute sind nicht daran interessiert, ihren Fehler zu beheben. ... und die Cygwin-Leute sagen, dass sie Bug-by-Bug-Linux-kompatibel sind ;-)
schily
2
Dieses Verhalten ist in der Single Unix-Spezifikation erforderlich . Es gibt einen 32-Bit-Wert, ja, aber dieser Wert enthält ein 8-Bit-Bitfeld, das die unteren 8 Bits des Werts von enthält _exit. Bitte verlinken Sie den FreeBSD-Fehlerbericht, auf den Sie sich beziehen. Vielleicht verstehe ich das von Ihnen beschriebene Problem falsch.
Random832
2
Das OP hat die Frage mit bash markiert und Bash im Fragetext erwähnt. Bash ist eine von Bourne abgeleitete Shell. ${.sh.}Variablen werden nicht unterstützt . Es ist jedoch richtig, dass Sie "Bourne" und nicht "von Bourne abgeleitet" sagen (obwohl Sie ksh93 einschließen).
Bis auf weiteres angehalten.
2
Diese Antwort scheint sehr spezifisch für Ihre spezielle Variante eines von SVR4 abgeleiteten Unix zu sein. Machen Sie sich bitte klarer darüber, was portabel ist und was nicht, und denken Sie daran, dass es "die" Bourne-Shell nicht gibt, es sei denn, Sie meinen die in V7.
zwol
4
Im Gegenteil, ich glaube, Sie sind es, die die Variationsbreite, insbesondere die historische Variation, verstehen. Man /bin/shkann sich darauf verlassen, dass sich diese speziellen Exit-Codes plattformübergreifend konsistent verhalten, was jedoch nicht zutrifft. (Es ist mir egal, ob ein bestimmtes System /bin/shals "echte Bourne-Shell" bezeichnet werden kann. Viel wichtiger ist zu wissen, dass nichts davon in POSIX vorkommt und dass die meisten Dinge, die Sie als "echte Unix-Systeme" bezeichnen, nicht funktionieren.) /bin/sh
Ich kann
6

Für Shell-Skripte habe ich manchmal das Shell-Äquivalent sysexist.hmit Shell-reservierten Exit-Codes (mit Präfix S_EX_), die ich benannt habe , in den Quellcode eingefügtexit.sh

Es ist im Grunde:

EX_OK=0 # successful termination 
EX__BASE=64     # base value for error messages 
EX_USAGE=64     # command line usage error 
EX_DATAERR=65   # data format error 
EX_NOINPUT=66   # cannot open input 
EX_NOUSER=67    # addressee unknown 
EX_NOHOST=68    # host name unknown 
EX_UNAVAILABLE=69       # service unavailable 
EX_SOFTWARE=70  # internal software error 
EX_OSERR=71     # system error (e.g., can't fork) 
EX_OSFILE=72    # critical OS file missing 
EX_CANTCREAT=73 # can't create (user) output file 
EX_IOERR=74     # input/output error 
EX_TEMPFAIL=75  # temp failure; user is invited to retry 
EX_PROTOCOL=76  # remote error in protocol 
EX_NOPERM=77    # permission denied 
EX_CONFIG=78    # configuration error 
EX__MAX=78      # maximum listed value 

#System errors
S_EX_ANY=1      #Catchall for general errors
S_EX_SH=2       #Misuse of shell builtins (according to Bash documentation); seldom seen
S_EX_EXEC=126   #Command invoked cannot execute         Permission problem or command is not an executable
S_EX_NOENT=127  #"command not found"    illegal_command Possible problem with $PATH or a typo
S_EX_INVAL=128  #Invalid argument to exit       exit 3.14159    exit takes only integer args in the range 0 - 255 (see first footnote)                                                                                        
#128+n  Fatal error signal "n"  kill -9 $PPID of script $? returns 137 (128 + 9)                               
#255*   Exit status out of range        exit -1 exit takes only integer args in the range 0 - 255              
S_EX_HUP=129                                                                                                   
S_EX_INT=130   
#...

Und kann generiert werden mit:

#!/bin/sh
src=/usr/include/sysexits.h
echo "# Generated from \"$src\"" 
echo "# Please inspect the source file for more detailed descriptions"
echo
< "$src" sed -rn 's/^#define  *(\w+)\s*(\d*)/\1=\2/p'| sed 's:/\*:#:; s:\*/::'
cat<<'EOF'

#System errors
S_EX_ANY=1  #Catchall for general errors
S_EX_SH=2   #Misuse of shell builtins (according to Bash documentation); seldom seen
S_EX_EXEC=126   #Command invoked cannot execute     Permission problem or command is not an executable
S_EX_NOENT=127  #"command not found"    illegal_command Possible problem with $PATH or a typo
S_EX_INVAL=128  #Invalid argument to exit   exit 3.14159    exit takes only integer args in the range 0 - 255 (see first footnote)
#128+n  Fatal error signal "n"  kill -9 $PPID of script $? returns 137 (128 + 9)
#255*   Exit status out of range    exit -1 exit takes only integer args in the range 0 - 255
EOF
$(which kill) -l |tr ' ' '\n'| awk '{ printf "S_EX_%s=%s\n", $0, 128+NR; }'

Ich benutze es zwar nicht viel, aber was ich benutze, ist eine Shell-Funktion, die Fehlercodes in ihre Zeichenfolgenformate umkehrt. Ich habe es benannt exit2str. Vorausgesetzt, Sie haben den obigen exit.shGenerator benannt exit.sh.sh, kann der Code für exit2strmit ( exit2str.sh.sh) generiert werden :

#!/bin/sh
echo '
exit2str(){
  case "$1" in'
./exit.sh.sh | sed -nEe's|^(S_)?EX_(([^_=]+_?)+)=([0-9]+).*|\4) echo "\1\2";;|p'
echo "
  esac
}"

Ich verwende dies in PS1meiner interaktiven Shell, damit ich nach jedem Befehl, den ich ausführe, den Beendigungsstatus und die Zeichenfolgenform sehen kann (falls eine bekannte Zeichenfolgenform vorliegt):

[15:58] pjump@laptop:~ 
(0=OK)$ 
[15:59] pjump@laptop:~ 
(0=OK)$ fdsaf
fdsaf: command not found
[15:59] pjump@laptop:~ 
(127=S_NOENT)$ sleep
sleep: missing operand
Try 'sleep --help' for more information.
[15:59] pjump@laptop:~ 
(1=S_ANY)$ sleep 100
^C
[15:59] pjump@laptop:~ 
(130=S_INT)$ sleep 100
^Z
[1]+  Stopped                 sleep 100
[15:59] pjump@laptop:~ 
(148=S_TSTP)$

Um diese zu bekommen, benötigen Sie ein Insourcable für die exit2str Funktion:

$ ./exit2str.sh.sh > exit2str.sh #Place this somewhere in your PATH

und verwenden Sie es dann in Ihrem ~/.bashrc, um den Exit-Code an jeder Eingabeaufforderung zu speichern und zu übersetzen und ihn als Eingabeaufforderung anzuzeigen ( PS1):

    # ...
    . exit2str.sh
PROMPT_COMMAND='lastStatus=$(st="$?"; echo -n "$st"; str=$(exit2str "$st") && echo "=$str"); # ...'
    PS1="$PS1"'\n($lastStatus)\$'
    # ...                                                                                   

Es ist sehr praktisch, um zu beobachten, wie einige Programme den Exit-Code-Konventionen folgen und andere nicht, um sich mit Exit-Code-Konventionen vertraut zu machen oder einfach nur zu sehen, was leichter vor sich geht. Nachdem ich es einige Zeit benutzt habe, kann ich sagen, dass viele systemorientierte Shell-Skripte den Konventionen folgen. EX_USAGEBesonders häufig ist, obwohl andere Codes, nicht viel. Ich versuche von Zeit zu Zeit die Konventionen zu befolgen, obwohl es immer $S_EX_ANY(1) für faule Leute gibt (ich bin einer).

PSkocik
quelle
Ich frage mich, ob es so etwas wie eine Zuordnung zwischen einem Fehlernummercode und einem Beendigungscode gibt, die verwendet werden kann, wenn der mit diesem Fehlernummercode gemeldete Fehler zu einem Fehlerbeendigung führt. Möglicherweise muss ich mir eine vernünftige Zuordnung überlegen.
PSkocik
1
Beeindruckend! Ich hatte nicht mit einer so ausführlichen Antwort gerechnet. Ich werde das auf jeden Fall ausprobieren, um zu sehen, wie sich die verschiedenen Befehle verhalten. Vielen Dank.
Anthony G - Gerechtigkeit für Monica
4

Solange Sie Ihre Beendigungscodes so dokumentieren, dass Sie sich in einem Jahr daran erinnern, wann Sie zurückkommen und das Skript optimieren müssen, ist alles in Ordnung. Die Idee der "reservierten Exit-Codes" gilt eigentlich nur, um zu sagen, dass es üblich ist, sie 0als Erfolgscode und alles andere als Fehlercode zu verwenden.

David King
quelle
4

Die beste Referenz, die ich finden konnte, war: http://tldp.org/LDP/abs/html/exitcodes.html

Demzufolge:

1 ist ein allgemeiner Fehlerfang, und ich habe immer gesehen, dass er für benutzerdefinierte Fehler verwendet wird.

2 ist für den Missbrauch von Shell-Built-Ins gedacht, z. B. für einen Syntaxfehler

Um Ihre Frage direkt zu beantworten, ist Ihr Skript mit den reservierten Fehlercodes in Ordnung. Es funktioniert wie erwartet, vorausgesetzt, Sie behandeln den Fehler basierend auf dem Fehlercode = 1/2/3.

Es wäre jedoch möglicherweise verwirrend, wenn Sie auf jemanden stoßen, der die reservierten Fehlercodes kennt und verwendet, was ziemlich selten zu sein scheint.

Eine andere Option, die Ihnen zur Verfügung steht, besteht darin, den Fehler zu wiederholen, falls einer vorliegt, und ihn dann zu beenden, vorausgesetzt, Ihr Skript folgt der Linux-Konvention "Keine Nachrichten sind gute Nachrichten", und es wird nichts für den Erfolg ausgegeben.

if [ $? -ne 0 ];then
    echo "Error type"
    exit 1
fi
Centimane
quelle
2

Basierend auf den Antworten, die ich erhalten habe (es war schwierig, eine über die anderen zu wählen), ist es nicht schädlich , bestimmte Arten von Fehlern durch die Verwendung eines Exit-Codes anzuzeigen, den Bash auch verwendet. Bash (oder eine andere Unix-Shell) führt keine besonderen Aktionen aus (z. B. das Ausführen von Ausnahmehandlern), wenn ein Benutzerskript mit einem dieser Fehlercodes beendet wird.

Es scheint, dass der Autor des Advanced Bash-Scripting Guide den BSD-Versuchen zur Standardisierung von Exit-Codes ( sysexits.h) zustimmt und einfach empfiehlt, dass Benutzer beim Schreiben von Shell-Skripten keine Exit-Codes angeben, die bereits mit vordefinierten Exit-Codes in Konflikt stehen im Einsatz, dh, sie beschränken ihre benutzerdefinierten Beendigungscodes auf die 50 verfügbaren Statuscodes im Bereich von 64 bis 113.

Ich schätze die Idee (und das Grundprinzip), aber ich hätte es vorgezogen, wenn der Autor expliziter gesagt hätte, dass es nicht schädlich ist, den Rat zu ignorieren - abgesehen von Fällen, in denen der Konsument eines Skripts nach Fehlern sucht, wie im zitierten Beispiel von 127 ( command not found).

Relevante POSIX-Spezifikationen

Ich habe nachgeforscht, was POSIX über Exit-Codes zu sagen hat, und die POSIX-Spezifikation scheint mit dem Autor des Advanced Bash-Scripting Guide übereinzustimmen. Ich habe die relevanten POSIX-Spezifikationen zitiert (Schwerpunkt meine):

Beenden Sie den Status für Befehle

Jeder Befehl hat einen Exit-Status, der das Verhalten anderer Shell-Befehle beeinflussen kann. Der Beendigungsstatus von Befehlen, die keine Dienstprogramme sind, wird in diesem Abschnitt dokumentiert. Der Beendigungsstatus der Standarddienstprogramme ist in den entsprechenden Abschnitten dokumentiert.

Wenn ein Befehl nicht gefunden wird, lautet der Beendigungsstatus 127. Wenn der Befehlsname gefunden wird, es sich jedoch nicht um ein ausführbares Dienstprogramm handelt, lautet der Beendigungsstatus 126. Anwendungen, die Dienstprogramme aufrufen, ohne die Shell zu verwenden, sollten diese Beendigungsstatuswerte verwenden ähnliche Fehler zu melden.

Wenn ein Befehl während der Worterweiterung oder -umleitung fehlschlägt, muss sein Beendigungsstatus größer als Null sein.

Intern muss die Shell für die Entscheidung, ob ein Befehl mit einem Nicht-Null-Beendigungsstatus beendet wird, den gesamten Statuswert erkennen, der für den Befehl durch das Äquivalent des Makros wait () function WEXITSTATUS (wie im Volume System Interfaces von definiert) abgerufen wurde POSIX.1-2008). Wenn der Exit-Status mit dem speziellen Parameter "?" Gemeldet wird, meldet die Shell die vollen acht verfügbaren Bits des Exit-Status. Der Beendigungsstatus eines Befehls, der beendet wurde, weil er ein Signal empfangen hat, wird als größer als 128 gemeldet.

Das exitDienstprogramm

Wie in anderen Abschnitten erläutert, wurden bestimmte Exit-Statuswerte für spezielle Zwecke reserviert und sollten von Anwendungen nur für die folgenden Zwecke verwendet werden:

  • 126 - Es wurde eine auszuführende Datei gefunden, die jedoch kein ausführbares Dienstprogramm ist.
  • 127 - Ein auszuführendes Dienstprogramm wurde nicht gefunden.
  • >128 - Ein Befehl wurde durch ein Signal unterbrochen.

Weitere Informationen

Für das, was es wert ist, war ich in der Lage, alle bis auf einen der Exit-Codes mit speziellen Bedeutungen zu überprüfen . Diese Tabelle mit Exit-Codes ist nützlich, da sie weitere Details und Beispiele zum Generieren der in der Bash-Referenz dokumentierten Fehlercodes enthält .

Versuchen Sie, den Exit-Status 128 zu generieren

Mit den Bash-Versionen 3.2.25 und 4.2.46 habe ich versucht, einen 128 Invalid argument to exitFehler zu werfen , aber jedes Mal, wenn ich eine 255 erhalten habe (Exit-Status außerhalb des gültigen Bereichs). Wird exit 3.14159die Shell beispielsweise als Teil eines Shell-Skripts oder in einer interaktiven untergeordneten Shell ausgeführt, wird die Shell mit dem folgenden Code beendet 255:

$ exit 3.14159
exit
bash: exit: 3.14159: numeric argument required

Um noch mehr Spaß zu haben, habe ich auch versucht, ein einfaches C-Programm auszuführen. In diesem Fall hat die exit(3)Funktion den float-Wert einfach vor dem Beenden in einen int-Wert (in diesem Fall 3) konvertiert:

#include <stdlib.h>
main()
{
    exit(3.14159);
}
Anthony G - Gerechtigkeit für Monica
quelle