“Piège… INT TERM EXIT” vraiment nécessaire?

63

De nombreux exemples à traputiliser trap ... INT TERM EXITpour les tâches de nettoyage. Mais est-il vraiment nécessaire d'énumérer les trois sigspecs?

Le manuel dit:

Si SIGNAL_SPEC est EXIT (0), ARG est exécuté à la sortie du shell.

que je crois s’applique que le script se termine normalement ou qu’il se termine parce qu’il a reçu SIGINTou SIGTERM. Une expérience confirme également ma conviction:

$ cat ./trap-exit
#!/bin/bash
trap 'echo TRAP' EXIT
sleep 3
$ ./trap-exit & sleep 1; kill -INT %1
[1] 759
TRAP
[1]+  Interrupt               ./trap-exit
$ ./trap-exit & sleep 1; kill -TERM %1
[1] 773
TRAP
[1]+  Terminated              ./trap-exit

Alors pourquoi tant d’exemples énumèrent-ils tous INT TERM EXIT? Ou ai-je oublié quelque chose et y a-t-il un cas où une semelle EXITmanquerait?

musiphil
la source
3
Gardez également à l'esprit qu'avec une spécification telle que INT TERM EXITle code de nettoyage est exécuté deux fois quand SIGTERMou SIGINTest reçu.
maxschlepzig

Réponses:

19

La spécification POSIX ne dit pas grand-chose sur les conditions qui entraînent l'exécution de l'interruption EXIT, mais uniquement sur l'apparence de son environnement lors de son exécution.

Dans le shell de cendres de Busybox, votre test de sortie de trappe ne fait pas écho à "TRAP" avant de quitter en raison de SIGINT ou de SIGTERM. Je soupçonne qu’il existe d’autres coquilles qui pourraient ne pas fonctionner de la même manière.

# /tmp/test.sh & sleep 1; kill -INT %1
# 
[1]+  Interrupt                  /tmp/test.sh
# 
# 
# /tmp/test.sh & sleep 1; kill -TERM %1
# 
[1]+  Terminated                 /tmp/test.sh
# 
Shawn J. Goff
la source
3
dashaussi ne pas piéger juste au moment EXIToù il reçoit SIGINT/SIGTERM.
maxschlepzig
4
zshainsi - peut bash- être est-il le seul shell où correspond EXITégalement les signaux.
maxschlepzig
@maxschlepzig n'intercepte zshpas EXITquand il reçoit INT, mais quand il reçoit TERM. EDIT: Je viens de remarquer quel âge c'était ...
JoL
27

Oui, il y a une différence.

Ce script se fermera lorsque vous appuierez sur Enter, ou l'enverrez SIGINTou SIGTERM:

trap '' EXIT
echo ' --- press ENTER to close --- '
read response

Ce script se ferme lorsque vous appuyez sur Enter:

trap '' EXIT INT TERM
echo ' --- press ENTER to close --- '
read response

* Testé dans sh , Bash et Zsh . (ne fonctionne plus dans sh lorsque vous ajoutez une commande pour que trap s'exécute)


Il y a aussi ce que @Shawn a dit: Ash et Dash ne piègent pas les signaux avec EXIT.

Donc, pour gérer les signaux de manière robuste, il est préférable d’éviter EXITtout interception et d’utiliser quelque chose comme ceci:

cleanup() {
    echo "Cleaning stuff up..."
    exit
}

trap cleanup INT TERM
echo ' --- press ENTER to close --- '
read var
cleanup
Zaz
la source
1
La solution avec le nettoyage fait la bonne chose - très élégante! C'est devenu un idiome pour mes scripts bash avec des mktempappels.
Bjoern Dahlgren
2
Est-ce exitnécessaire dans cleanup?
Jarno
3
Cela ne fonctionne pas si votre code contient des erreurs shellscript entraînant sa fermeture prématurée.
lundi
2
@ijw: Dans Bash et Ksh, vous pouvez vous en servir ERR, mais ce n'est pas portable .
Zaz
5
Cette solution n'est pas robuste lorsqu'un autre shell l'appelle. Il ne gère pas l' attente à la sortie de la coopérative ; En trap - INT TERM; kill -2 $$tant que dernière ligne de nettoyage, vous souhaiterez dire au shell parent qu’il s’est arrêté prématurément. Si un shell parent foobar.sh appelle votre script (foo.sh), puis bar.sh, vous ne voulez pas que bar.sh soit exécuté si INT / TERM est envoyé à votre foo.sh. trap cleanup EXITgérera automatiquement cette propagation, ce qui en fait l’IMO la plus robuste. Cela signifie également que vous n’auriez pas à appeler cleanupà la fin du script.
Nicholas Pipitone
12

Affiner la dernière réponse, car elle a des problèmes:

# Our general exit handler
cleanup() {
    err=$?
    echo "Cleaning stuff up..."
    trap '' EXIT INT TERM
    exit $err 
}
sig_cleanup() {
    trap '' EXIT # some shells will call EXIT after the INT handler
    false # sets $?
    cleanup
}
trap cleanup EXIT
trap sig_cleanup INT QUIT TERM

Points ci-dessus:

Les gestionnaires INT et TERM ne s'arrêtent pas pour moi lorsque je teste - ils gèrent l'erreur, puis le shell retourne à la sortie (et ce n'est pas trop surprenant). Je m'assure donc que le nettoyage se termine par la suite et, dans le cas des signaux, utilise toujours un code d'erreur (et dans l'autre cas, une sortie normale conserve le code d'erreur).

Avec bash, il semble que quitter dans le gestionnaire INT appelle également le gestionnaire EXIT. C'est pourquoi je détache le gestionnaire de sortie et l'appelle moi-même (ce qui fonctionnera dans n'importe quel shell, quel que soit le comportement).

J'interromps l'exit parce que les scripts de shell peuvent se terminer avant qu'ils atteignent le bas - erreurs de syntaxe, set -e et un retour différent de zéro, appelant simplement l'exit. Vous ne pouvez pas compter sur un shellscript pour aller au fond.

SIGQUIT est Ctrl- \ si vous ne l'avez jamais essayé. Vous obtient un bonus coredump. Donc, je pense que cela vaut également la peine d'être piégé, même si c'est un peu obscur.

L’expérience passée montre que si vous (comme moi) appuyez toujours sur Ctrl-C à plusieurs reprises, vous l’accepterez parfois à mi-chemin de la partie nettoyage de votre script shell, donc cela fonctionne mais pas toujours aussi parfaitement que vous le souhaitez.

ijw
la source
2
L'appelant serait tout simplement obtenir 1 comme code de sortie, peu importe quel signal provoqué la sortie, tandis que withour trapl'appelant obtiendrait 130 pour SIGINT, 143 pour SIGTERM, etc. Donc , je capturer et transmettre le code de sortie correcte comme: sig_cleanup() { err=$?; trap '' EXIT; (exit $err); cleanup; }.
musiphil
2
Pouvez-vous préciser le but de trap '' EXIT INT TERMla fonction de nettoyage? Est-ce pour empêcher l'utilisateur d'interrompre accidentellement le nettoyage que vous avez mentionné dans le dernier paragraphe? Le EXITredondant n'est-il pas ?
Six