Utilisation de codes "réservés" pour l'état de sortie des scripts shell

Je suis récemment tombé sur cette list de codes de sortie avec des significations spéciales du Guide Bash-Scripting avancé. Ils se réfèrent à ces codes comme étant réservés et recommandnt que:

Selon le tableau ci-dessus, les codes de sortie 1-2, 126-165 et 255 ont des significations particulières et doivent donc être évités pour les parameters de sortie spécifiés par l'user.

Il y a quelques time, j'ai écrit un script qui utilisait les codes de sortie suivants:

  • 0 – succès
  • 1 – nom d'hôte incorrect
  • 2 – arguments invalides spécifiés
  • 3 – privilèges d'user insuffisants

Lorsque j'ai écrit le script, je n'étais pas au courant de codes de sortie spéciaux, donc j'ai simplement commencé à 1 pour la première condition d'erreur et j'ai incrémenté le statut de sortie pour chaque type d'erreur successif.

J'ai écrit le script avec l'intention qu'à un stade ultérieur il pourrait être appelé par d'autres scripts (qui pourraient vérifier les codes de sortie non-zéro). Je ne l'ai pas encore fait. jusqu'à présent, j'ai seulement exécuté le script de mon shell interactif (Bash) et je me demandais si des problèmes pouvaient être causés en utilisant mes codes de sortie personnalisés. Quelle est la pertinence / importance de la recommandation du Guide Bash-Scripting Avancé?

Je n'ai trouvé aucun conseil corroborant dans la documentation de Bash; sa section sur l' état de sortie énumère simplement les codes de sortie utilisés par Bash, mais n'indique pas que ceux-ci sont réservés ou mettent en garde contre l'utilisation de ceux-ci pour vos propres scripts / programmes.

Plusieurs tentatives ont été faites pour normaliser la signification des codes de sortie de process. En plus de celui que vous mentionnez, je connais:

  • les BSD ont sysexits.h qui définit les significations pour les valeurs à partir de 64 ans.

  • GNU grep documents qui sortent du code 0 signifie qu'au less une correspondance a été trouvée, 1 signifie qu'aucune correspondance n'a été trouvée et 2 une erreur d'E / S s'est produite; cette convention est évidemment aussi utile pour d'autres programmes pour lesquels la distinction entre «rien ne s'est mal passé mais je n'ai rien trouvé» et «une erreur d'E / S survenue» est significative.

  • De nombreuses implémentations du system fonction de bibliothèque C utilisent le code de sortie 127 pour indiquer que le programme n'existe pas ou n'a pas pu démarrer.

  • Sous Windows, les codes NTSTATUS (qui sont NTSTATUS répartis dans l'espace des nombres à 32 bits) peuvent être utilisés comme codes de sortie, en particulier ceux qui indiquent qu'un process a été interrompu en raison d'un comportement catastrophique (par exemple STATUS_STACK_OVERFLOW ).

Vous ne pouvez pas countr sur un programme donné obéissant à l'une de ces conventions. La seule règle fiable est que le code de sortie 0 est le succès et que tout le rest est une sorte d'échec. (Notez que la valeur EXIT_SUCCESS de C89 n'est pas garantie, mais exit(0) doit se comporter de manière identique pour exit(EXIT_SUCCESS) même si les valeurs ne sont pas les mêmes.)

Aucun code de sortie n'a une signification particulière, mais la valeur dans $? peut avoir une signification particulière.

La façon dont Bourne Shell et ksh93 ont traité et transmis les codes de sortie et les situations d'erreur à la variable shell $? c'est le problème. Contrairement à ce que vous listz, seulement les valeurs suivantes pour $? avoir une signification particulière:

  • 126 Impossible d'exécuter le binary même s'il existe
  • 127 Le binary spécifié n'existe pas
  • 128 le statut de sortie était == 0 mais il existe un problème non spécifié

De plus, il existe un shell spécifique et une plage de $? codes> 128 réservés à un programme qui a été interrompu par un signal:

  • Bourne Shell bash et ksh88 utilisent 128 + numéro de signal
  • ksh93 utilise 256 + numéro de signal.

D'autres valeurs ne posent pas de problèmes car elles peuvent être distinguées de la coquille-spéciale $? valeurs.

En particulier, les valeurs 1 et 2 ne sont pas utilisées pour des conditions spéciales, mais sont simplement des codes de sortie utilisés par des commands embeddedes qui pourraient agir de la même façon lorsqu'elles ne sont pas embeddedes. Il semble donc que le pointeur vers le bash scripting guide que vous avez fourni n'est pas un bon manuel car il list simplement les codes utilisés par bash sans commenter si un code spécifique est une valeur spéciale qui devrait être évitée pour ses propres scripts.

Les nouvelles versions de Bourne Shell utilisent waitid() au lieu de waitpid() pour attendre la fin du programme et waitid() (introduit en 1989 pour SVr4) utilise une meilleure interface syscall (similaire à celle utilisée par UNOS en 1980).

Comme les nouvelles versions de Bourne Shell encodent la raison de sortie dans une variable séparée ${.sh.code} / ${.sh.codename} que le code de sortie qui se trouve dans ${.sh.status} / ${.sh.termsig} , voir http://schillix.sourceforge.net/man/man1/bosh.1.html , le code de sortie n'est pas surchargé avec des états spéciaux et, à la suite de l'utilisation de `waitid (), le Bourne Shell supporte maintenant le return tous les 32 bits du code de sortie – pas seulement les 8 bits bas.

BTW: attention à ne pas exit(256) ou similaire à partir d'un programme C ou d'un script shell, car il en résulte $? étant interprété comme 0 dans une coquille classique.

La meilleure reference que j'ai pu find était la suivante: http://tldp.org/LDP/abs/html/exitcodes.html

Selon ce:

1 est un catchall général pour les erreurs, et je l'ai toujours vu utilisé pour les erreurs définies par l'user.

2 est pour une mauvaise utilisation de shell ins ins, comme une erreur de syntaxe

Pour répondre directement à votre question, votre script fonctionnera correctement en utilisant les codes d'erreur réservés, il fonctionnera comme prévu en supposant que vous gériez l'erreur en fonction du code d'erreur = 1/2/3.

Cependant, il serait peut-être déroutant si vous rencontrez quelqu'un qui connaît et utilise les codes d'erreur réservés, ce qui semble assez rare.

Une autre option à votre disposition est de faire écho à l'erreur s'il y en a un, puis de quitter, en supposant que votre script respecte la convention Linux «aucune nouvelle n'est une bonne nouvelle» et écho n'est rien en cas de succès.

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

Tant que vous documentez vos codes de sortie afin que vous vous souveniez d'eux dans un an à partir de maintenant quand vous devez revenir et modifier le script, tout ira bien. L'idée de «codes de sortie réservés» ne s'applique plus vraiment que de dire qu'il est d'usage d'utiliser 0 comme code de réussite et n'importe quoi d'autre comme code d'échec.

Pour les scripts shell, j'ai parfois dans la source l'équivalent de sysexist.h avec des codes de sortie réservés au shell (préfixés avec S_EX_ ), que j'ai nommé exit.sh

C'est essentiellement:

 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 (eg, 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 listd 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 #... 

Et peut être généré avec:

 #!/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; }' 

Je ne l'utilise pas beaucoup, cependant, mais ce que j'utilise est une fonction shell qui inverse les codes d'erreur à leurs formats de string. Je l'ai nommé exit2str . En supposant que vous ayez nommé le exit.sh générateur exit.sh.sh ci-dessus, le code pour exit2str peut être généré avec ( exit2str.sh.sh ):

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

Je l'utilise dans la PS1 de mon shell interactif pour que, après chaque command que je lance, je puisse voir son statut de sortie et sa forme de string (si elle a une forme de string connue):

 [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)$ 

Pour les get, vous avez besoin d'un insourcable pour la fonction exit2str:

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

puis l'utiliser dans votre ~/.bashrc pour save et traduire le code de sortie sur chaque invite de command et afficher votre message d'invite ( PS1 ):

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

C'est très pratique d'observer comment certains programmes suivent les conventions de code de sortie et d'autres non, pour en savoir plus sur les conventions de code de sortie, ou simplement pour voir ce qui se passe plus facilement. Après l'avoir utilisé pendant un certain time, je peux dire que de nombreux scripts shell orientés système suivent les conventions. EX_USAGE est particulièrement commun, bien que d'autres codes, pas beaucoup. J'essaie de suivre les conventions de time en time, bien qu'il y ait toujours $S_EX_ANY (1) pour les gens paresseux (j'en suis un).

Sur la base des réponses que j'ai reçues (il était difficile d'en choisir une par rapport aux autres), il n'est pas dangereux d'indiquer certains types d'erreurs en utilisant un code de sortie que Bash utilise également. Bash (ou tout autre shell Unix) ne fera rien de spécial (comme l'exécution de gestionnaires d'exceptions) si un script user se termine par l'un de ces codes d'erreur.

Il semble que l'auteur du Advanced Bash-Scripting Guide soit d'accord avec BSD pour normaliser les codes de sortie ( sysexits.h ) et recommand simplement que lorsque les users écrivent des scripts shell, ils ne spécifient pas les codes de sortie codes de sortie déjà utilisés, c'est-à-dire qu'ils limitent leurs codes de sortie personnalisés aux 50 codes d'état disponibles dans la plage 64-113.

J'apprécie l'idée (et la logique) mais j'aurais préféré que l'auteur soit plus explicite qu'il ne soit pas dangereux d'ignorer les conseils – à part les cas où le consommateur d'un script vérifie des erreurs telles que l'exemple cité de 127 ( command not found ).

Spécifications POSIX pertinentes

J'ai étudié ce que POSIX a à dire à propos des codes de sortie et la spécification POSIX semble concorder avec l'auteur du Advanced Bash-Scripting Guide. J'ai cité les spécifications POSIX pertinentes (accentuation mine):

Statut de sortie pour les commands

Chaque command a un statut de sortie qui peut influencer le comportement des autres commands shell. Le statut de sortie des commands qui ne sont pas des utilitaires est documenté dans cette section. Le statut de sortie des utilitaires standard est documenté dans leurs sections respectives.

Si une command est introuvable, l'état de sortie doit être 127. Si le nom de la command est trouvé mais pas un utilitaire exécutable, l'état de sortie doit être 126. Les applications qui invoquent des utilitaires sans utiliser le shell doivent utiliser ces valeurs d'état de sortie pour signaler des erreurs similaires.

Si une command échoue pendant l'extension ou la redirection de mots, son état de sortie doit être supérieur à zéro.

En interne, le shell doit reconnaître la totalité de la valeur d'état récupérée pour la command par l'équivalent de la macro WEXITSTATUS de la fonction wait () (telle que définie dans le volume System Interfaces de POSIX.1-2008). Lors du signalement de l'état de sortie avec le paramètre spécial '?', Le shell doit indiquer les huit bits de sortie disponibles. Le statut de sortie d'une command qui s'est terminée parce qu'il a reçu un signal doit être supérieur à 128.

L'utilitaire de exit

Comme expliqué dans d'autres sections, certaines valeurs d'état de sortie ont été réservées pour des utilisations spéciales et devraient être utilisées par des applications uniquement à ces fins:

  • 126 – Un file à exécuter a été trouvé, mais ce n'était pas un utilitaire exécutable.
  • 127 – Un utilitaire à exécuter n'a pas été trouvé.
  • >128 – Une command a été interrompue par un signal.

Plus d'informations

Pour ce que ça vaut, j'ai pu vérifier tous sauf un de la list des codes de sortie avec des significations spéciales . Cette table des codes de sortie est utile car elle fournit plus de détails – et des exemples sur la façon de générer les codes d'erreur documentés dans la reference Bash .

Tentative de générer un état de sortie de 128

En utilisant Bash versions 3.2.25 et 4.2.46, j'ai essayé de jeter un 128 Invalid argument to exit erreur mais chaque fois que j'ai reçu un 255 (état de sortie hors de scope). Par exemple, si la exit 3.14159 est exécutée dans le cadre d'un script shell ou dans un shell enfant interactif, le shell se termine par un code de 255 :

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

Pour encore plus de plaisir, j'ai aussi essayé d'exécuter un simple programme C mais dans ce cas, il semble que la fonction exit(3) simplement converti le float en int (3 dans ce cas) avant de sortir:

 #include <stdlib.h> main() { exit(3.14159); }