J'ai écrit un script bash simple avec une boucle pour imprimer la date et envoyer un ping à une machine distante :
#!/bin/bash
while true; do
# *** DATE: Thu Sep 17 10:17:50 CEST 2015 ***
echo -e "\n*** DATE:" `date` " ***";
echo "********************************************"
ping -c5 $1;
done
Lorsque je l'exécute depuis un terminal, je ne peux pas l'arrêter avec Ctrl+C .
Il semble qu'il envoie le ^C au terminal, mais le script ne s'arrête pas.
MacAir:~ tomas$ ping-tester.bash www.google.com
*** DATE: Thu Sep 17 23:58:42 CEST 2015 ***
********************************************
PING www.google.com (216.58.211.228): 56 data bytes
64 bytes from 216.58.211.228: icmp_seq=0 ttl=55 time=39.195 ms
64 bytes from 216.58.211.228: icmp_seq=1 ttl=55 time=37.759 ms
^C <= That is Ctrl+C press
--- www.google.com ping statistics ---
2 packets transmitted, 2 packets received, 0.0% packet loss
round-trip min/avg/max/stddev = 40.887/59.699/78.510/18.812 ms
*** DATE: Thu Sep 17 23:58:48 CEST 2015 ***
********************************************
PING www.google.com (216.58.211.196): 56 data bytes
64 bytes from 216.58.211.196: icmp_seq=0 ttl=55 time=37.460 ms
64 bytes from 216.58.211.196: icmp_seq=1 ttl=55 time=37.371 ms
Peu importe combien de fois j'appuie dessus ou à quelle vitesse je le fais. Je ne suis pas capable de l'arrêter.
Faites le test et réalisez par vous-même.
En guise de solution secondaire, je l'arrête avec Ctrl+Z , qui l'arrête puis kill %1
.
Que se passe-t-il exactement ici avec ^C ?
Réponse acceptée :
Ce qui se passe, c'est que les deux bash
et ping
recevoir le SIGINT (bash
n'étant pas interactif, les deux ping
et bash
exécuté dans le même groupe de processus qui a été créé et défini comme groupe de processus de premier plan du terminal par le shell interactif à partir duquel vous avez exécuté ce script).
Cependant, bash
gère ce SIGINT de manière asynchrone, uniquement après la sortie de la commande en cours d'exécution. bash
ne sort qu'à la réception de ce SIGINT si la commande en cours d'exécution meurt d'un SIGINT (c'est-à-dire que son état de sortie indique qu'il a été tué par SIGINT).
$ bash -c 'sh -c "trap exit\ 0 INT; sleep 10; :"; echo here'
^Chere
Ci-dessus, bash
, sh
et sleep
recevoir SIGINT lorsque j'appuie sur Ctrl-C, mais sh
sort normalement avec un code de sortie 0, donc bash
ignore le SIGINT, c'est pourquoi nous voyons "ici".
ping
, du moins celui d'iputils, se comporte comme ça. Lorsqu'il est interrompu, il imprime des statistiques et se termine avec un état de sortie 0 ou 1 selon que ses pings ont été répondus ou non. Ainsi, lorsque vous appuyez sur Ctrl-C tout en ping
est en cours d'exécution, bash
note que vous avez appuyé sur Ctrl-C
dans ses gestionnaires SIGINT, mais depuis ping
sort normalement, bash
ne quitte pas.
Si vous ajoutez un sleep 1
dans cette boucle et appuyez sur Ctrl-C
pendant sleep
est en cours d'exécution, car sleep
n'a pas de gestionnaire spécial sur SIGINT, il mourra et rapportera à bash
qu'il est mort d'un SIGINT, et dans ce cas bash
se terminera (il se tuera en fait avec SIGINT afin de signaler l'interruption à son parent).
Pourquoi bash
se comporte comme ça, je ne suis pas sûr et je note que le comportement n'est pas toujours déterministe. Je viens de poser la question sur le bash
liste de diffusion de développement (mise à jour :@Jilles a maintenant identifié la raison dans sa réponse).
Le seul autre shell que j'ai trouvé qui se comporte de la même manière est ksh93 (mise à jour, comme mentionné par @Jilles, il en va de même pour FreeBSD sh
). Là, SIGINT semble être clairement ignoré. Et ksh93
quitte chaque fois qu'une commande est tuée par SIGINT.
Vous obtenez le même comportement que bash
ci-dessus mais aussi :
ksh -c 'sh -c "kill -INT \$\$"; echo test'
N'affiche pas "test". C'est-à-dire qu'il se termine (en se tuant avec SIGINT ici) si la commande qu'il attendait meurt de SIGINT, même si lui-même n'a pas reçu ce SIGINT.
Une solution consisterait à ajouter un :
trap 'exit 130' INT
En haut du script pour forcer bash
pour quitter à la réception d'un SIGINT (notez que dans tous les cas, SIGINT ne sera pas traité de manière synchrone, uniquement après la sortie de la commande en cours d'exécution).
Idéalement, nous voudrions signaler à nos parents que nous sommes morts d'un SIGINT (de sorte que si c'est un autre bash
script par exemple, que bash
le script est également interrompu). Faire une exit 130
n'est pas la même chose que de mourir de SIGINT (bien que certains shells définissent $?
à la même valeur pour les deux cas), mais il est souvent utilisé pour signaler un décès par SIGINT (sur les systèmes où SIGINT est 2, ce qui est le plus).
Cependant pour bash
, ksh93
ou FreeBSD sh
, ça ne marche pas. Ce statut de sortie 130 n'est pas considéré comme un décès par SIGINT et un script parent ne le serait pas abandonner ici.
Donc, une alternative peut-être meilleure serait de nous tuer avec SIGINT après avoir reçu SIGINT :
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT