GNU/Linux >> Tutoriels Linux >  >> Linux

Impossible d'arrêter un script Bash avec Ctrl+c ?

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

Connexe :#!/bin/bash - aucun fichier ou répertoire de ce type ?

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

Linux
  1. Quel interpréteur Shell exécute un script sans Shebang ?

  2. nom de base avec des espaces dans un script bash ?

  3. Appelez le script Python à partir de bash avec un argument

  4. Autorisation refusée avec bash.sh pour exécuter cron

  5. Exécutez la commande bash sur le pipeline jenkins

Comment écrire un script bash avec des exemples

Shell Scripting Partie I :Premiers pas avec les scripts bash

35 exemples de scripts bash

Bash Beginner Series #10 :Automatisation avec Bash

Tutoriel d'introduction aux scripts Bash avec 5 exemples pratiques

Script bash pour la boucle expliqué avec des exemples