Code de sortie par défaut lorsque le processus est terminé?


54

Lorsqu'un processus est tué avec un signal gérable tel que SIGINTou SIGTERMmais qu'il ne gère pas le signal, quel sera le code de sortie du processus?

Qu'en est-il des signaux impossibles à gérer, comme SIGKILL?

D'après ce que je peux dire, tuer un processus avec des SIGINTrésultats probables dans un code de sortie 130, mais est-ce que cela varie en fonction de la mise en œuvre du noyau ou du shell?

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

Je ne sais pas comment tester les autres signaux ...

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

1
vos killall myScripttravaux, d’où le retour du killall (et non du script!) à 0. Vous pouvez placer un kill -x $$[x représentant le numéro du signal et $$ généralement étendu par le shell au PID de ce script (fonctionne dans sh, bash, ...)] à l' intérieur du script, puis testez son noyau de sortie.
Olivier Dulac


commentaire sur la demi-question: ne mettez pas myScript en arrière-plan. (omettre &). Envoyez le signal depuis un autre processus shell (dans un autre terminal), que vous pourrez utiliser $?après la fin de myScript.
MattBianco

Réponses:


61

Les processus peuvent appeler l'appel _exit()système (sous Linux, voir aussi exit_group()) avec un argument entier pour signaler un code de sortie à leur parent. Bien qu'il s'agisse d'un entier, seuls les 8 bits les moins significatifs sont disponibles pour le parent (à l'exception de l' utilisation de waitid()ou du gestionnaire sur SIGCHLD dans le parent pour récupérer ce code , mais pas sous Linux).

Les parents font généralement un wait()ou waitpid()pour obtenir le statut de leur enfant sous forme d’entier (bien qu’une waitid()sémantique quelque peu différente puisse également être utilisée).

Sous Linux et la plupart des Unices, si le processus s'est terminé normalement, les bits 8 à 15 de ce numéro d' état contiendront le code de sortie tel que transmis exit(). Si ce n'est pas le cas, les 7 bits les moins significatifs (0 à 6) contiendront le numéro du signal et le bit 7 sera activé si un cœur a été vidé.

perl's $?par exemple contient ce nombre tel que défini par 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

Les shells de type Bourne définissent également le statut de sortie de la dernière commande d'exécution dans leur propre $?variable. Cependant, il ne contient pas directement le nombre retourné par waitpid(), mais une transformation, et il est différent entre les shells.

Ce qui est commun entre tous les shells est qu’il $?contient les 8 bits les plus bas du code de sortie (le nombre transmis à exit()) si le processus s’est terminé normalement.

La différence est lorsque le processus est terminé par un signal. Dans tous les cas, et cela est requis par POSIX, le nombre sera supérieur à 128. POSIX ne spécifie pas quelle peut être la valeur. En pratique cependant, dans tous les shells de type Bourne que je connais, les 7 bits les plus bas $?contiendront le numéro du signal. Mais, où nest le numéro de signal,

  • en ash, zsh, pdksh, bash, le shell Bourne $?est 128 + n. Cela signifie que dans ces coquilles, si vous obtenez un $?de 129, vous ne savez pas si c'est parce que le processus s'est terminé avec exit(129)ou s'il a été tué par le signal 1( HUPsur la plupart des systèmes). Mais la raison en est que les shells, lorsqu'ils sortent d'eux-mêmes, retournent par défaut le statut de sortie de la dernière commande sortie. En s'assurant que rien $?n'est jamais supérieur à 255, cela permet d'avoir un statut de sortie cohérent:

    $ 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, $?est 256 + n. Cela signifie que $?vous pouvez faire la différence entre un processus tué et non tué à partir d’une valeur de celui -ci. Les versions plus récentes de ksh, si exit $?était supérieur à 255, se tuent avec le même signal afin de pouvoir signaler le même état de sortie à son parent. Bien que cela semble être une bonne idée, cela signifie que kshcela générera un core dump supplémentaire (potentiellement écrasant l’autre) si le processus était tué par un signal générateur de core:

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

    Là où vous pourriez même dire qu'il y a un bogue, c'est qu'il se ksh93tue, même s'il $?vient d'un return 257fait fait par une fonction:

    $ 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. yashoffre un compromis. Ça revient 256 + 128 + n. Cela signifie que nous pouvons également faire la différence entre un processus tué et un qui s'est terminé correctement. Et lors de la sortie, il fera rapport 128 + nsans avoir à se suicider et les effets secondaires qu'il peut avoir.

    $ 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
    

Pour obtenir le signal de la valeur de $?, la méthode portable consiste à utiliser kill -l:

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

(pour la portabilité, vous ne devez jamais utiliser les numéros de signal, seulement les noms de signal)

Sur les fronts non-Bourne:

  • csh/ tcshEt fishmême que le shell Bourne , sauf que l'état est au $statuslieu de $?(notez que zshdéfinit également $statusla compatibilité avec csh(en plus $?)).
  • rc: l'état de sortie est $statuségalement présent, mais lorsqu'il est tué par un signal, cette variable contient le nom du signal (comme sigtermou sigill+coresi un noyau a été généré) au lieu d'un nombre, ce qui est une autre preuve du bon design de ce shell .
  • es. le statut de sortie n'est pas une variable. Si vous en tenez compte, vous exécutez la commande en tant que:

    status = <={cmd}
    

    qui retournera un nombre ou sigtermou sigsegv+corecomme dans rc.

Peut-être par souci d’exhaustivité, nous devrions mentionner zshles tableaux de type ' $pipestatuset bash' $PIPESTATUSqui contiennent l’état de sortie des composants du dernier pipeline.

Et aussi par souci d'exhaustivité, en ce qui concerne les fonctions shell et les fichiers sources, les fonctions retournent par défaut avec le statut de sortie de la dernière commande exécutée, mais peuvent également définir explicitement un statut de retour avec la commande returnintégrée. Et nous voyons quelques différences ici:

  • bashet mksh(depuis R41, une régression ^ Wchange apparemment introduite intentionnellement ) tronquera le nombre (positif ou négatif) à 8 bits. Ainsi, par exemple return 1234, définissez $?sur 210, return -- -1définissez $?sur 255.
  • zshet pdksh(et les dérivés autres que mksh) autorisent tout entier décimal signé 32 bits signé (-2 31 à 2 31 -1) (et tronquent le nombre à 32 bits ).
  • ashet yashautorise tout nombre entier positif compris entre 0 et 2 31 -1 et renvoie une erreur pour tout nombre en dehors de celui-ci.
  • ksh93pour return 0au return 320jeu $?tel qu'il est, mais pour tout le reste, troncature à 8 bits. Attention, comme déjà mentionné, le fait de renvoyer un nombre compris entre 256 et 320 pourrait provoquer kshsa mort à la sortie.
  • rcet espermettent de retourner n'importe quoi, même des listes.

Notez également que certains shells utilisent également des valeurs spéciales $?/ $statuspour signaler certaines conditions d'erreur qui ne sont pas le statut de sortie d'un processus, comme 127ou 126pour une commande introuvable ou non exécutable (ou une erreur de syntaxe dans un fichier source) ...


1
an exit code to their parentet to get the *status* of their child. vous avez mis l'accent sur "statut". Est exit codeet *status*le même? Cas oui, quelle est l'origine d'avoir deux noms? Cas pas pareil, pourriez-vous donner la définition / référence du statut?
n611x007

2
Il y a 3 chiffres ici. Le code de sortie : le numéro passé à exit(). L' état de sortie : le numéro obtenu, waitpid()qui comprend le code de sortie, le numéro du signal et s'il y a eu ou non un noyau vidé. Et le nombre que certaines coques rendent disponible dans l'une de leurs variables spéciales ( $?, $status) est une transformation de l' état de sortie de telle sorte qu'il contienne le code de sortie en cas de terminaison normale, mais transporte également des informations de signal si le processus a été tué (celui-ci est généralement appelé statut de sortie ). Tout cela est expliqué dans ma réponse.
Stéphane Chazelas

1
Je vois Merci! J'apprécie vraiment cette note explicite de la distinction ici. Ces expressions concernant la sortie sont utilisées de manière interchangeable à certains endroits, cela vaut la peine de le faire. La variante de la variable shell a-t-elle même un nom (général)? Donc, je suggérerais de le clarifier explicitement avant d' entrer dans les détails sur les coquilles. Je suggèrerais d'insérer l'explication (à partir de votre commentaire) après votre premier ou deuxième paragraphe.
n611x007

1
Pouvez-vous citer la citation POSIX qui dit que les 7 premiers bits constituent le signal? Tout ce que j'ai pu trouver, c'est la > 128partie: "L'état de sortie d'une commande qui s'est terminée parce qu'elle a reçu un signal doit être signalé comme étant supérieur à 128." pubs.opengroup.org/onlinepubs/9699919799/utilities/…
Ciro Santilli a annoncé

1
@cuonglm, je ne pense pas qu'il soit disponible publiquement ailleurs sur HTTP, vous pouvez toujours l'obtenir à partir de gmane via NNTP. Recherchez l'identifiant du message efe764d811849b34eef24bfb14106f61@austingroupbugs.net(à partir du 06/05/2015) ouXref: news.gmane.org gmane.comp.standards.posix.austin.general:10726
Stéphane Chazelas

23

Lorsqu'un processus se termine, il renvoie une valeur entière au système d'exploitation. Sur la plupart des variantes unix, cette valeur est prise modulo 256: tout sauf les bits de poids faible est ignoré. Le statut d’un processus enfant est renvoyé à son parent via un entier de 16 bits dans lequel

  • les bits 0 à 6 (les 7 bits de poids faible) correspondent au numéro de signal utilisé pour tuer le processus, ou à 0 si le processus s'est terminé normalement;
  • le bit 7 est mis à 1 si le processus a été tué par un signal et un noyau vidé;
  • les bits 8 à 15 représentent le code de sortie du processus si le processus s'est terminé normalement, ou 0 si le processus a été tué par un signal.

Le statut est renvoyé par l' waitappel système ou l'un de ses frères et soeurs. POSIX ne spécifie pas le codage exact de l'état de sortie et du numéro de signal; il ne fournit que

  • un moyen de savoir si l'état de sortie correspond à un signal ou à une sortie normale;
  • un moyen d'accéder au code de sortie, si le processus s'est terminé normalement;
  • un moyen d'accéder au numéro du signal, si le processus a été tué par un signal.

À proprement parler, il n'y a pas de code de sortie lorsqu'un processus est tué par un signal: il existe plutôt un statut de sortie .

Dans un script shell, l' état de sortie d'une commande est signalé via la variable spéciale $?. Cette variable code l'état de sortie de manière ambiguë:

  • Si le processus s'est terminé normalement, $?son statut de sortie est atteint.
  • Si le processus a été tué par un signal, le nombre $?est 128 plus le numéro du signal sur la plupart des systèmes. POSIX n'impose que des mandats $?supérieurs à 128 dans ce cas; ksh93 ajoute 256 au lieu de 128. Je n'ai jamais vu de variante unix qui ne fasse rien d'autre que d'ajouter une constante au numéro du signal.

Ainsi, dans un script shell, vous ne pouvez pas déterminer avec certitude si une commande a été supprimée par un signal ou si elle a été supprimée avec un code d'état supérieur à 128, sauf avec ksh93. Il est très rare que des programmes quittent avec des codes d'état supérieurs à 128, en partie parce que les programmeurs l'évitent en raison de l' $?ambiguïté.

SIGINT est le signal 2 sur la plupart des variantes Unix, donc $?128 + 2 = 130 pour un processus qui a été tué par SIGINT. Vous verrez 129 pour SIGHUP, 137 pour SIGKILL, etc.


Bien mieux formulé et plus pertinent que le mien même s'il dit essentiellement les mêmes choses. Vous voudrez peut-être préciser que cela $?concerne uniquement les coquilles de type Bourne. Voir aussi yashpour un comportement différent (mais toujours POSIX). En outre, comme pour POSIX + XSI (Unix), a kill -2 "$pid"enverra un SIGINT au processus, mais le numéro de signal réel peut ne pas être 2, alors $? ne sera pas nécessairement 128 + 2 (ou 256 + 2 ou 384 + 2), mais kill -l "$?"reviendra INT, c'est pourquoi je conseillerais pour la portabilité de ne pas se référer aux numéros eux-mêmes.
Stéphane Chazelas

8

Cela dépend de votre coquille. De la bash(1)page de manuel, section SHELL GRAMMAR, sous- section Commandes simples :

La valeur de retour d'une commande simple est [...] 128+ n si la commande est terminée par le signal n .

Comme SIGINTle signal 2 est sur votre système, la valeur de retour est 130 lorsqu’il est exécuté sous Bash.


1
Comment trouvez-vous cela dans le monde ou même savez-vous où chercher? Je m'incline devant ton génie.
Cory Klein

1
@CoryKlein: Expérience, principalement. Oh, et vous voudrez probablement la signal(7)page de manuel également.
Ignacio Vazquez-Abrams

truc cool; Savez-vous si j'ai inclus des fichiers en C avec ces constantes par hasard? +1
Rui F Ribeiro le

@CoryKlein Pourquoi n'avez-vous pas choisi cette réponse?
Rui F Ribeiro le

3

Il semble être le bon endroit pour mentionner que SVr4 a introduit waitid () en 1989, mais aucun programme important ne semble l’utiliser jusqu’à présent. waitid () permet de récupérer les 32 bits complets du code exit ().

Il y a environ 2 mois, j'ai réécrit la partie wait / job control du Bourne Shell afin qu'elle utilise waitid () au lieu de waitpid (). Cela a été fait afin de supprimer la limitation qui masque le code de sortie avec 0xFF.

L’interface waitid () est bien plus propre que les implémentations précédentes de wait (), à l’exception de l’appel cwait () de UNOS à partir de 1980.

Vous pouvez être intéressé par la page de manuel à:

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

et vérifiez la section "Substitution de paramètres" à la page 8.

Les nouvelles variables .sh. * Ont été introduites pour l'interface waitid (). Cette interface n'a plus de signification ambiguë pour les nombres connus pour $? et rendre l'interfaçage beaucoup plus facile.

Notez que vous devez disposer d'un waitid () compatible POSIX pour pouvoir utiliser cette fonctionnalité. Par conséquent, Mac OS X et Linux ne l'offrent pas actuellement, mais le waitid () est émulé sur l'appel waitpid (), donc une plate-forme non-POSIX, vous ne recevrez toujours que 8 bits du code de sortie.

En bref: .sh.status est le code de sortie numérique, .sh.code est la raison de la sortie numérique.

Pour une meilleure portabilité, il existe: .sh.codename pour la version textuelle du motif de sortie, par exemple "DUMPED" et .sh.termsig, nom unique du signal qui a mis fin au processus.

Pour une meilleure utilisation, il existe deux valeurs .sh.codename non liées à l'exit: "NOEXEC" et "NOTFOUND" qui sont utilisées lorsqu'un programme ne peut pas être lancé du tout.

FreeBSD a corrigé leur bug kerlnel waitid () dans les 20 heures suivant mon rapport, Linux n’ayant pas encore démarré avec leur correctif. J'espère que 26 ans après l'introduction de cette fonctionnalité présente dans POSIX, tous les systèmes d'exploitation le supporteront bientôt.


Une réponse connexe est unix.stackexchange.com/a/453432/5132 .
JdeBP
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.