Conserver les codes de sortie lors de la capture de SIGINT et similaire?

14

Si j'utilise trapcomme décrit par exemple sur http://linuxcommand.org/wss0160.php#trap pour attraper ctrl-c (ou similaire) et nettoyer avant de quitter, je change le code de sortie retourné.

Maintenant, cela ne fera probablement pas de différence dans le monde réel (par exemple parce que les codes de sortie ne sont pas portables et en plus de cela pas toujours sans ambiguïté comme discuté dans Code de sortie par défaut lorsque le processus est terminé? ) Mais je me demande toujours s'il y a vraiment aucun moyen d'empêcher cela et de retourner le code d'erreur par défaut pour les scripts interrompus à la place?

Exemple (en bash, mais ma question ne doit pas être considérée comme spécifique à bash):

#!/bin/bash
trap 'echo EXIT;' EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. ' _
trap 'echo SIGINT; exit 1;' INT
read -p 'If you ctrl-c me now my return code will be 1. ' _

Production:

$ ./test.sh # doing ctrl-c for 1st read
If you ctrl-c me now my return code will be the default for SIGTERM.
$ echo $?
130
$ ./test.sh # doing ctrl-c for 2nd read
If you ctrl-c me now my return code will be the default for SIGTERM.
If you ctrl-c me now my return code will be 1. SIGINT 
EXIT
$ echo $?
1

(Modifié pour le supprimer afin de le rendre plus conforme à POSIX.)

(Modifié à nouveau pour en faire un script bash à la place, ma question n'est pas spécifique au shell.)

Édité pour utiliser le "INT" portable pour le piège en faveur du "SIGINT" non portable.

Modifié pour supprimer les accolades inutiles et ajouter une solution potentielle.

Mettre à jour:

Je l'ai résolu maintenant en quittant simplement avec certains codes d'erreur codés en dur et en piégeant EXIT. Cela peut être problématique sur certains systèmes car le code d'erreur peut différer ou le piège EXIT n'est pas possible, mais dans mon cas, c'est assez bien.

trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
phk
la source
Votre script semble un peu étrange: vous dites readde lire à partir du coprocessus actuel et trap cmd SIGINTne fonctionnera pas comme le standard dit que vous devez utiliser trap cmd INT.
schily
Ah oui, sous POSIX c'est bien sûr sans le préfixe SIG.
phk
Oups, mais "read -p" ne serait pas supporté non plus, donc je vais l'adapter pour bash.
phk
@schily: Je ne sais pas ce que tu veux dire par "coprocessus".
phk
Eh bien, la page de manuel de Korn Shell indique la read -plecture des entrées du coprocessus actuel.
schily

Réponses:

4

Tout ce que vous devez faire est de changer le gestionnaire EXIT dans votre gestionnaire de nettoyage. Voici un exemple:

#!/bin/bash
cleanup() {
    echo trapped exit
    trap 'exit 0' EXIT
}
trap cleanup EXIT
read -p 'If you ctrl-c me now my return code will be the default for SIGTERM. '
rocheux
la source
vouliez-vous dire trap cleanup INTau lieu de trap cleanup EXIT?
Jeff Schaller
Je pense que je voulais dire EXIT. Lorsque exit est finalement appelé, quelle que soit la procédure, nous changeons le trap pour quitter avec return 0. Je ne pense pas que les gestionnaires de signaux soient récursifs.
rocky
OK, dans votre exemple, je ne suis pas du tout en train de piéger (SIG) INT, ce qui est en fait ce que je veux pour faire un peu de nettoyage même si l'utilisateur quitte mon script interactif via ctrl-c.
phk
@phk Ok. Je pense que si vous avez l'idée de forcer la sortie à retourner 0 ou une autre valeur, ce que j'ai compris était le problème. Je suppose que vous serez en mesure d'ajuster le code pour mettre le paramètre trap EXIT dans votre véritable gestionnaire de nettoyage appelé par SIGINT.
rocheux
@rocky: Mon objectif était d'avoir un gestionnaire de trap sans changer le code de sortie que j'aurais normalement sans le gestionnaire. Maintenant, je pouvais simplement retourner le code de sortie que vous obtiendriez normalement, mais le problème était que je ne connais pas le code de sortie exact dans ces cas (comme on le voit dans le fil auquel j'ai lié et le message de meuh, c'est assez compliqué). Votre message était toujours utile, je n'ai pas pensé à redéfinir dynamiquement le gestionnaire de trap.
phk
9

En fait, l'interruption de l'interne de bash readsemble être un peu différente de l'interruption d'une commande exécutée par bash. Normalement, lorsque vous entrez trap, $?est défini et vous pouvez le conserver et quitter avec la même valeur:

trap 'rc=$?; echo $rc SIGINT; exit $rc' INT
trap 'rc=$?; echo $rc EXIT; exit $rc' EXIT

Si votre script est interrompu lors de l'exécution d'une commande comme sleep ou même d'une commande intégrée wait, vous verrez

130 SIGINT
130 EXIT

et le code de sortie est 130. Cependant, pour read -p, il semble $?être 0 (de toute façon sur ma version de bash 4.3.42).


Le traitement des signaux pendant readpourrait être en cours, selon le fichier des changements dans ma version ... (/ usr / share / doc / bash / CHANGES)

les changements entre cette version, bash-4.3-alpha, et la version précédente, bash-4.2-release.

  1. Nouvelles fonctionnalités de Bash

    r. En mode Posix, la «lecture» est interruptible par un signal piégé. Après avoir exécuté le gestionnaire d'interruptions, la lecture renvoie un signal 128 + et supprime toute entrée partiellement lue.

meuh
la source
OK, c'est vraiment bizarre. C'est 130 sur le shell interactif sur bash 4.3.42 (sous cygwin), 0 sous le même shell en mode script et POSIX ou pas ne fait aucune différence. Mais ensuite sous dash et busybox c'est toujours 1.
phk
J'ai essayé le mode POSIX avec un piège qui ne se ferme pas et il redémarre read, comme indiqué dans le fichier CHANGES (ajouté à ma réponse). Donc, ce pourrait être un travail en cours.
meuh
Le code de sortie 130 est 100% non portable. Alors que Bourne Shell utilise 128 + signocomme code de sortie pour les signaux, ksh93 utilise 256 + signo. POSIX dit: quelque chose au-dessus de 128 ....
schily
@schily True, comme indiqué dans le fil auquel j'ai lié dans le message d'origine ( unix.stackexchange.com/questions/99112 ).
phk
6

Tout code de sortie de signal habituel sera disponible lors $?de l'entrée dans le gestionnaire de déroutement:

sig_handler() {
    exit_status=$?  # Eg 130 for SIGINT, 128 + (2 == SIGINT)
    echo "Doing signal-specific up"
    exit "$exit_status"
}
trap sig_handler INT HUP TERM QUIT

S'il existe une interruption EXIT distincte, vous pouvez utiliser la même méthodologie: conserver immédiatement le statut de sortie transmis par le gestionnaire de signaux (s'il y en a un), puis renvoyer le statut de sortie enregistré.

Tom Hale
la source
Cela s'applique à la plupart des coquilles? Très agréable. localn'est pas POSIX cependant.
phk
1
Édité. Je ne l'ai pas testé ailleurs {ba,z}sh. AFAIK, c'est le mieux qui puisse être fait, peu importe.
Tom Hale
Cela ne fonctionne pas dans le tiret ( $?est 1 quel que soit le signal reçu).
MoonSweep
2

Il suffit de renvoyer un code d'erreur ne suffit pas pour simuler une sortie par SIGINT. Je suis surpris que personne n'ait mentionné cela jusqu'à présent. Pour en savoir plus: https://www.cons.org/cracauer/sigint.html

La bonne façon est:

for sig in EXIT ABRT HUP INT PIPE QUIT TERM; do
    trap "cleanup;
          [ $sig  = EXIT ] && normal_exit_only_cleanup;
          [ $sig != EXIT ] && trap - $sig EXIT && kill -s $sig $$
         " $sig
done

Cela fonctionne avec Bash, Dash et zsh. Pour plus de portabilité, vous devez utiliser des spécifications de signal numérique (d'un autre côté, zsh attend un paramètre de chaîne pour la killcommande ...)

Notez également le traitement spécial du EXITsignal. Cela est dû au fait que certains shells (à savoir Bash) exécutent également des interruptions EXITpour tout signal (après des interruptions éventuellement définies sur ce signal). La réinitialisation du EXITpiège empêche cela.

Exécution de code uniquement à la sortie "normale"

La vérification [ $sig = EXIT ]permet d'exécuter du code uniquement à la sortie normale (non signalée). Cependant, tous les signaux doivent avoir des pièges qui réinitialisent enfin le piège EXIT; normal_exit_only_cleanupsera également appelé pour les signaux qui ne le font pas. Il sera également exécuté par une sortie via set -e. Cela peut être résolu en interceptant ERR(qui n'est pas pris en charge par Dash) et en ajoutant une vérification [ $sig = ERR ]avant le kill.

Version simplifiée Bash uniquement

D'un autre côté, ce comportement signifie que dans Bash, vous pouvez simplement faire

trap cleanup EXIT

pour simplement exécuter du code de nettoyage et conserver l'état de sortie.

édité

  • Élaborer le comportement de Bash de "EXIT piège tout"

  • Supprimer le signal KILL, qui ne peut pas être piégé

  • Supprimer les préfixes SIG des noms de signaux

  • N'essayez pas de kill -s EXIT

  • Prendre set -een compte / ERR

philipp2100
la source
Il n'y a aucune exigence générale qu'un programme qui intercepte les signaux se termine d'une manière qui préserve le fait qu'il a reçu un signal. Par exemple, un programme peut déclarer que l'envoi SIGINTest le moyen de l'arrêter et il peut décider de quitter avec 0 s'il a réussi à se terminer sans erreur ou un autre code d'erreur s'il ne s'est pas arrêté proprement. Exemple: top. Exécutez top; echo $?, puis appuyez sur Ctrl-C. Le statut affiché à l'écran sera 0.
Louis
1
Merci d'avoir fait remarquer cela. Cependant, l'affiche a spécifiquement demandé de conserver le code de sortie.
philipp2100