Lorsqu'un processus est tué avec un signal gérable comme SIGINT
ou SIGTERM
mais qu'il ne gère pas le signal, quel sera le code de sortie du processus?
Qu'en est-il des signaux non gérables comme SIGKILL
?
D'après ce que je peux dire, tuer un processus avec SIGINT
entraîne probablement un code de sortie 130
, mais cela varierait-il selon le noyau ou la mise en œuvre du shell?
$ cat myScript
#!/bin/bash
sleep 5
$ ./myScript
<ctrl-c here>
$ echo $?
130
Je ne sais pas comment je testerais les autres signaux ...
$ ./myScript &
$ killall myScript
$ echo $?
0 # duh, that's the exit code of killall
$ killall -9 myScript
$ echo $?
0 # same problem
Les processus peuvent appeler l'appel système _exit()
(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 (exception à cela lorsque en utilisant waitid()
ou un gestionnaire sur SIGCHLD dans le parent pour récupérer ce code , mais pas sous Linux).
Le parent fera généralement un wait()
ou waitpid()
pour obtenir le statut de leur enfant sous forme d'entier (bien que waitid()
avec une sémantique quelque peu différente peut également être utilisé).
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 transmis à exit()
. Si ce n'est pas le cas, les 7 bits les moins significatifs (0 à 6) contiendront le numéro de signal et le bit 7 sera défini si un cœur a été vidé.
Perl
$?
contient par exemple ce nombre 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 font également l'état de sortie de la dernière commande exécutée dans leur propre variable $?
. Cependant, il ne contient pas directement le nombre renvoyé par waitpid()
, mais une transformation sur celui-ci, et c'est différent entre les shells.
Ce qui est commun à tous les shells, c'est que $?
Contient les 8 bits les plus bas du code de sortie (le nombre passé à exit()
) si le processus s'est terminé normalement.
Là où il diffère, c'est quand le processus se termine par un signal. Dans tous les cas, et c'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 de $?
Contiendront le numéro de signal. Mais, où n
est le numéro du signal,
dans ash, zsh, pdksh, bash, le Bourne Shell, $?
est 128 + n
. Cela signifie que dans ces shells, 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
(HUP
sur la plupart des systèmes). Mais la raison est que les shells, lorsqu'ils sortent d'eux-mêmes, retournent par défaut l'état de sortie de la dernière commande quittée. En vous assurant que $?
N'est jamais supérieur à 255, cela permet d'avoir un état 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 qu'à partir d'une valeur de $?
, Vous pouvez faire la différence entre un processus tué et non tué. Les versions plus récentes de ksh
, à la sortie, si $?
Était supérieur à 255, se tue 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 ksh
générera un vidage de mémoire supplémentaire (écrasant potentiellement l'autre) si le processus a été tué par un signal de génération de cœur:
$ 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 bug, c'est que ksh93
Se tue même si $?
Vient d'un return 257
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
. yash
offre un compromis. Il renvoie 256 + 128 + n
. Cela signifie que nous pouvons également faire la différence entre un processus interrompu et un processus qui s'est terminé correctement. Et à sa sortie, il signalera 128 + n
Sans avoir à se suicider ni à subir 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 à partir 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 de numéros de signaux, uniquement des noms de signaux)
Sur les fronts non Bourne:
csh
/tcsh
et fish
identiques au Bourne Shell, sauf que l'état est dans $status
au lieu de $?
(notez que zsh
définit également $status
pour la compatibilité avec csh
(en plus de $?
)).rc
: le statut de sortie est également dans $status
, mais lorsqu'elle est tuée par un signal, cette variable contient le nom du signal (comme sigterm
ou sigill+core
si un noyau a été généré) au lieu d'un nombre, ce qui est une autre preuve de la bonne conception de ce Shell.es
. l'état de sortie n'est pas une variable. Si vous vous en souciez, vous exécutez la commande en tant que:
status = <={cmd}
qui renverra un nombre ou sigterm
ou sigsegv+core
comme dans rc
.
Pour être complet, nous devrions mentionner les tableaux $pipestatus
De zsh
et $PIPESTATUS
De bash
qui contiennent l'état de sortie des composants du dernier pipeline.
Et également pour être complet, en ce qui concerne les fonctions Shell et les fichiers sources, par défaut, les fonctions retournent avec l'état de sortie de la dernière exécution de la commande, mais peuvent également définir un état de retour de manière explicite avec la fonction intégrée return
. Et nous voyons quelques différences ici:
bash
et mksh
(puisque R41, ne régression ^ Wchange apparemment introduite intentionnellement ) tronquera le nombre (positif ou négatif) à 8 bits. Ainsi, par exemple, return 1234
Définira $?
Sur 210
, return -- -1
Définira $?
Sur 255.zsh
et pdksh
(et les dérivés autres que mksh
) autorisent tout entier décimal 32 bits signé (-231 à 231-1) (et tronquer le nombre à 32 bits).ash
et yash
autorisent tout entier positif de 0 à 231-1 et renvoie une erreur pour tout nombre en dehors de cela.ksh93
Pour return 0
À return 320
Définissez $?
Tel quel, mais pour toute autre chose, tronquez à 8 bits. Attention, comme déjà mentionné, le fait de renvoyer un nombre compris entre 256 et 320 pourrait entraîner la mort de ksh
lors de sa sortie.rc
et es
permettent de renvoyer n'importe quoi même des listes.Notez également que certains shells utilisent également des valeurs spéciales de $?
/$status
Pour signaler certaines conditions d'erreur qui ne sont pas le statut de sortie d'un processus, comme 127
Ou 126
pour commande non trouvée ou non exécutable (ou erreur de syntaxe dans un fichier source) ...
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 16 bits dans lequel
Le statut est retourné par l'appel système wait
ou l'un de ses frères et sœurs. POSIX ne spécifie pas l'encodage exact de l'état de sortie et du numéro de signal; il fournit seulement
À strictement parler, il n'y a pas de code de sortie lorsqu'un processus est tué par un signal: ce qu'il y a à la place est une sortie état .
Dans un script Shell, le état de sortie d'une commande est signalé via la variable spéciale $?
. Cette variable code le statut de sortie de manière ambiguë:
$?
est son état de sortie.$?
est 128 plus le numéro de signal sur la plupart des systèmes. POSIX exige uniquement que $?
est supérieur à 128 dans ce cas; ksh93 ajoute 256 au lieu de 128. Je n'ai jamais vu de variante Unix qui ait fait autre chose que d'ajouter une constante au numéro de signal.Ainsi, dans un script Shell, vous ne pouvez pas dire avec certitude si une commande a été tuée par un signal ou sortie avec un code d'état supérieur à 128, sauf avec ksh93. Il est très rare que des programmes se terminent avec des codes d'état supérieurs à 128, en partie parce que les programmeurs l'évitent en raison de $?
ambiguïté.
SIGINT est le signal 2 sur la plupart des variantes Unix, donc $?
est 128 + 2 = 130 pour un processus qui a été tué par SIGINT. Vous verrez 129 pour SIGHUP, 137 pour SIGKILL, etc.
Cela dépend de votre Shell. Depuis la page de manuel bash(1)
, Shell GRAMMAR section, Simple Commands sous-section:
La valeur de retour d'une commande simple est [...] 128 + n si la commande se termine par le signal n .
Puisque SIGINT
sur votre système est le signal numéro 2, la valeur de retour est 130 lorsqu'il est exécuté sous Bash.
Cela 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 de contrôle d'attente/travail du Bourne Shell pour utiliser 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 beaucoup plus propre que les implémentations précédentes de wait () à l'exception de l'appel cwait () de UNOS de 1980.
Vous pouvez être intéressé à lire la page de manuel sur:
http://schillix.sourceforge.net/man/man1/bosh.1.html
et vérifiez la section "Substitution de paramètres" actuellement en 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 avoir un waitid () compatible POSIX pour pouvoir utiliser cette fonctionnalité, donc Mac OS X et Linux ne le proposent pas actuellement, mais le waitid () est émulé lors de l'appel de waitpid (), ainsi de suite un plate-forme non-POSIX, vous n'obtiendrez 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 sortie numérique.
Pour une meilleure portabilité, il existe: .sh.codename pour la version textuelle du motif de sortie, par ex. "DUMPED" et .sh.termsig, le nom unique du signal qui a mis fin au processus.
Pour une meilleure utilisation, il existe deux valeurs .sh.codename non liées à la sortie: "NOEXEC" et "NOTFOUND" qui sont utilisées lorsqu'un programme ne peut pas être lancé du tout.
FreeBSD a corrigé son bogue kerlnel waitid () dans les 20 heures suivant mon rapport, Linux n'a pas encore commencé avec son correctif. J'espère que 26 ans après avoir introduit cette fonctionnalité qui est maintenant dans POSIX, tous les OS la prendront en charge rapidement.