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

15

Je suis récemment tombé sur cette liste de codes de sortie avec des significations spéciales dans le guide avancé de Bash-Scripting. Ils se réfèrent à ces codes comme étant réservés et recommandent que:

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

Il y a quelque temps, j'ai écrit un script qui utilisait les codes d'état de sortie suivants:

  • 0 - succès
  • 1 - nom d'hôte incorrect
  • 2 - arguments non valides spécifiés
  • 3 - Privilèges utilisateur insuffisants

Lorsque j'ai écrit le script, je ne connaissais aucun code de sortie spécial, j'ai donc simplement commencé à 1 pour la première condition d'erreur et j'ai incrémenté l'état de sortie pour chaque type d'erreur successif.

J'ai écrit le script avec l'intention qu'à un stade ultérieur, il puisse être appelé par d'autres scripts (qui pourraient vérifier les codes de sortie non nuls). Je ne l'ai pas encore fait; Jusqu'à présent, je n'ai exécuté le script qu'à partir de mon shell interactif (Bash) et je me demandais quels problèmes / si des problèmes pouvaient être causés par l'utilisation de mes codes de sortie personnalisés. Quelle est la pertinence / l’importance de la recommandation du Guide de création de scripts avancés?

Je n'ai trouvé aucun conseil corroborant dans la documentation de Bash; sa section sur l' état de sortie répertorie simplement les codes de sortie utilisés par Bash, mais ne précise pas que ceux-ci sont réservés ou déconseille de les utiliser pour vos propres scripts / programmes.

Anthony G - justice pour Monica
la source
6
Moi et d'autres, je considère que l'ABSG est généralement de mauvaise qualité. À mon avis, l'auteur de la page que vous avez liée fait une affirmation non étayée que les codes de sortie répertoriés sont réservés en fonction, apparemment, du fait que le shell lui-même les utilise pour des significations spécifiques. Il y a eu des tentatives de création de normes pour les scripts, dont aucune n'a réussi. L'important est de documenter les codes d'erreur que vous choisissez afin que les consommateurs de vos scripts (par exemple d'autres scripts) sachent quoi faire en fonction d'eux.
pause jusqu'à nouvel ordre.
@DennisWilliamson Si vous postez votre commentaire comme réponse, je serais heureux de le voter; J'ai déjà voté toutes les autres réponses car j'ai trouvé chacune d'elles utile. Bien que votre réponse soit similaire dans son contenu à celle de David King (et dans une moindre mesure à Zwol), vous déclarez explicitement qu'il n'y a aucune preuve de l'affirmation dans la citation de l'ABSG.
Anthony G - justice pour Monica
1
Merci pour l'offre, mais je pense que mon commentaire doit rester tel quel.
pause jusqu'à nouvel ordre.
J'ai depuis découvert que la spécification POSIX comprend des conseils similaires, j'ai donc ajouté ces informations à ma propre réponse (contenant les résultats de mes recherches depuis que j'ai posé cette question).
Anthony G - justice pour Monica

Réponses:

10

Il y a eu plusieurs tentatives pour normaliser la signification des codes de sortie de processus. En plus de celle que vous mentionnez, je connais:

  • les BSD ont sysexits.hqui définit la signification des valeurs à partir de 64.

  • Les grepdocuments GNU indiquant que le code de sortie 0 signifie qu'au moins une correspondance a été trouvée, 1 signifie qu'aucune correspondance n'a été trouvée et 2 signifie qu'une erreur d'E / S s'est produite; cette convention est évidemment également 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 s'est produite" est significative.

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

  • Sous Windows, les NTSTATUScodes (qui sont dispersés de manière gênante sur tout l'espace numérique 32 bits) peuvent être utilisés comme codes de sortie, en particulier ceux qui indiquent qu'un processus a été interrompu en raison d'une mauvaise conduite catastrophique (par exemple STATUS_STACK_OVERFLOW).

Vous ne pouvez pas compter sur un programme donné obéissant à l'une quelconque de ces conventions. La seule règle fiable est que le code de sortie 0 est un succès et tout le reste est une sorte d'échec. (Notez que les C89 ne EXIT_SUCCESSsont pas garantis d'avoir la valeur zéro; cependant, ils exit(0)doivent se comporter de manière identique exit(EXIT_SUCCESS)même si les valeurs ne sont pas les mêmes.)

zwol
la source
Merci. Il a été difficile de choisir une réponse par rapport aux autres mais j'accepte celle-ci car elle a répondu à ma question tout en fournissant un large aperçu des différents codes de sortie utilisés (avec des liens pertinents): elle mérite plus que les 3 votes positifs qu'elle a actuellement.
Anthony G - justice pour Monica
11

Aucun code de sortie n'a une signification spéciale, mais la valeur dans $?peut avoir une signification spéciale.

La manière dont Bourne Shell et ksh93 ont traité et transmis les codes de sortie et les situations d'erreur à la variable shell $?est le problème. Contrairement à ce que vous indiquez, seules les valeurs suivantes $?ont une signification particulière:

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

De plus, il existe un shell non spécifié et une plage de $?codes spécifiques à la plate-forme > 128 qui sont réservés à un programme interrompu par un signal:

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

Les autres valeurs ne posent pas de problème car elles peuvent être distinguées des $?valeurs shell-special .

En particulier, les valeurs 1 et 2 ne sont pas utilisées pour des conditions spéciales, mais ne sont que des codes de sortie utilisés par des commandes intégrées qui pourraient agir de la même manière lorsqu'elles ne sont pas intégrées. Il semble donc que le pointeur vers le guide de script bash que vous avez fourni ne soit pas un bon manuel car il répertorie 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 versions plus récentes de Bourne Shell utilisent waitid()au lieu d' waitpid()attendre la fin du programme et waitid()(introduit en 1989 pour SVr4) utilise une meilleure interface syscall (similaire à celle qu'UNOS utilisait déjà en 1980).

Comme les nouvelles versions de Bourne Shell codent le motif de sortie dans une variable distincte ${.sh.code}/ ${.sh.codename}du 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, en raison de l'utilisation de `waitid (), le Bourne Shell prend désormais en charge le retour des 32 bits du code de sortie - pas seulement les 8 bits les plus faibles.

BTW: faites attention à ne pas exit(256)ressembler à un programme C ou à un script shell, car cela se traduit par $?être interprété comme 0 dans un shell classique.

schily
la source
2
BTW: J'ai fait un rapport de bogue contre FreeBSD et le noyau Linux pour ce waitid()bogue vers la fin mai. Les gens de FreeBSD ont résolu le problème en 20 heures, les gens de Linux ne sont pas intéressés à corriger leur bogue. ... et les gens de Cygwin disent qu'ils sont compatibles bug par bug Linux ;-)
schily
2
Ce comportement est requis par la spécification Unix unique. Il y a une valeur de 32 bits, oui, mais cette valeur contient un champ de bits de 8 bits contenant les 8 bits les plus faibles de la valeur de _exit. Veuillez lier le rapport de bogue FreeBSD auquel vous faites référence, peut-être que je comprends mal le problème que vous décrivez.
Random832
2
L'OP a marqué la question avec bash et a mentionné Bash dans le texte de la question. Bash est un shell dérivé de Bourne. Il ne prend pas en charge les ${.sh.}variables. Il est vrai, cependant, que vous dites "Bourne" et non "dérivé de Bourne" (bien que vous incluiez ksh93).
pause jusqu'à nouvel ordre.
2
Cette réponse semble être très spécifique à votre variante particulière de certains Unix dérivés SVR4. Veuillez être plus clair sur ce qui est portable et ce qui ne l'est pas, en gardant à l'esprit qu'il n'y a rien de tel que "le" shell Bourne, à moins que vous ne parliez de celui qui était en V7.
zwol
4
Au contraire, je crois que c'est vous qui sous-estimez la gamme de variation ici, en particulier la variation historique. Vous donnez l'impression que vous /bin/shpouvez vous fier à un comportement cohérent par rapport à ces codes de sortie spéciaux multiplateforme, ce qui n'est pas vrai. (Je me fiche de savoir si un système particulier /bin/shpeut être considéré comme un "vrai Bourne shell". Il est beaucoup plus important de savoir que rien de tout cela n'est dans POSIX, et que la plupart des choses que vous citez en tant que "vrais systèmes Unix" ne font pas ' t fournir un POSIX conforme de /bin/shtoute façon.)
zwol
6

Pour les scripts shell, j'ai parfois source l'équivalent shell sysexist.havec des codes de sortie réservés au shell (préfixés par S_EX_), que j'ai nommésexit.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 (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   
#...

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 chaîne. Je l'ai nommé exit2str. En supposant que vous avez nommé le exit.shgénérateur ci-dessus exit.sh.sh, le code pour exit2strpeut ê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
}"

J'utilise ceci dans le PS1de mon shell interactif pour qu'après chaque commande que j'exécute, je puisse voir son état de sortie et sa forme de chaîne (s'il a une forme de chaîne 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 obtenir, vous avez besoin d'un câble insourcable pour la fonction exit2str:

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

puis l'utiliser dans votre ~/.bashrcpour enregistrer et traduire le code de sortie sur chaque invite de commande et l'afficher votre invite ( PS1):

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

C'est assez pratique pour 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 temps, je peux dire que de nombreux scripts shell orientés système suivent les conventions. EX_USAGEest particulièrement assez commun, bien que d'autres codes, pas beaucoup. J'essaie de suivre les conventions de temps en temps, bien qu'il y ait toujours $S_EX_ANY(1) pour les paresseux (j'en suis un).

PSkocik
la source
Je me demande s'il y a quelque chose comme un mappage entre un code errno et un code de sortie à utiliser si l'erreur signalée avec ce code errno entraîne une sortie d'erreur. Je devrais peut-être trouver une cartographie raisonnable.
PSkocik
1
Hou la la! Je ne m'attendais pas à une réponse aussi élaborée. Je vais certainement essayer cela comme un bon moyen de voir comment les différentes commandes se comportent. Merci.
Anthony G - justice pour Monica
4

Tant que vous documentez vos codes de sortie afin de vous en souvenir dans un an, lorsque vous devrez revenir et modifier le script, tout ira bien. L'idée de "codes de sortie réservés" ne s'applique plus vraiment, si ce n'est de dire qu'il est habituel d'utiliser 0comme code de réussite et quoi que ce soit d'autre comme code d'échec.

David King
la source
4

La meilleure référence que j'ai pu trouver était la suivante: http://tldp.org/LDP/abs/html/exitcodes.html

Selon ce:

1 est un fourre-tout général pour les erreurs, et je l'ai toujours vu utilisé pour les erreurs définies par l'utilisateur.

2 est pour une mauvaise utilisation des commandes intégrées au shell, comme une erreur de syntaxe

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

Cependant, cela pourrait ê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 qui s'offre à vous est de faire écho à l'erreur s'il y en a une, puis de quitter, en supposant que votre script respecte la convention Linux "aucune nouvelle n'est une bonne nouvelle" et écho n'a rien en cas de succès.

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

D'après les réponses que j'ai reçues (il était difficile d'en choisir une parmi les 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 utilisateur se termine avec l'un de ces codes d'erreur.

Il semble que l'auteur du Guide de script Bash avancé soit d'accord avec les tentatives de BSD de normaliser les codes de sortie ( sysexits.h) et recommande simplement que lorsque les utilisateurs écrivent des scripts shell, ils ne spécifient pas déjà les codes de sortie qui entrent en conflit avec les codes de sortie prédéfinis en cours d'utilisation, 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 justification) mais j'aurais préféré que l'auteur soit plus explicite qu'il ne soit pas nuisible d'ignorer les conseils - à l'exception des cas où le consommateur d'un script vérifie les erreurs comme l'exemple cité de 127 ( command not found).

Spécifications POSIX pertinentes

J'ai recherché ce que POSIX a à dire sur les codes de sortie et la spécification POSIX semble être en accord avec l'auteur du Advanced Bash-Scripting Guide. J'ai cité les spécifications POSIX pertinentes (je souligne):

Statut de sortie pour les commandes

Chaque commande a un statut de sortie qui peut influencer le comportement des autres commandes shell. L'état de sortie des commandes qui ne sont pas des utilitaires est documenté dans cette section. L'état de sortie des utilitaires standard est documenté dans leurs sections respectives.

Si aucune commande n'est trouvée, l'état de sortie doit être 127. Si le nom de la commande est trouvé, mais qu'il ne s'agit pas d'un utilitaire exécutable, l'état de sortie doit être 126. Les applications qui appellent des utilitaires sans utiliser le shell doivent utiliser ces valeurs d'état de sortie pour signaler des erreurs similaires.

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

En interne, afin de décider si une commande se termine avec un état de sortie différent de zéro, le shell doit reconnaître la valeur d'état entière récupérée pour la commande 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 signaler les huit bits complets de l'état de sortie disponible. L'état de sortie d'une commande qui s'est terminée parce qu'elle a reçu un signal doit être signalé comme supérieur à 128.

L' exitutilité

Comme expliqué dans d'autres sections, certaines valeurs de statut de sortie ont été réservées à des usages spéciaux et ne doivent être utilisées par les applications qu'à ces fins:

  • 126 - Un fichier à exécuter a été trouvé, mais ce n'était pas un utilitaire exécutable.
  • 127 - Un utilitaire à exécuter est introuvable.
  • >128 - Une commande a été interrompue par un signal.

Plus d'informations

Pour ce que ça vaut, j'ai pu vérifier tout sauf un de la liste des codes de sortie avec des significations spéciales . Ce tableau des codes de sortie est utile car il fournit plus de détails et des exemples sur la façon de générer les codes d'erreur documentés dans la référence Bash .

Tentative de génération d'un état de sortie de 128

En utilisant les versions 3.2.25 et 4.2.46 de Bash, j'ai essayé de lancer une 128 Invalid argument to exiterreur mais à chaque fois que j'ai reçu un 255 (état de sortie hors plage). Par exemple, s'il exit 3.14159est exécuté dans le cadre d'un script shell ou dans un shell enfant interactif, le shell se termine avec un code de 255:

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

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

#include <stdlib.h>
main()
{
    exit(3.14159);
}
Anthony G - justice pour Monica
la source