Ce qui se passe est que les deux bash
et ping
reçoivent le SIGINT ( bash
étant pas interactif, à la fois ping
et bash
exécuter dans le même groupe de processus qui a été créé et ensemble comme groupe de processus de premier plan du terminal par le shell interactif vous avez exécuté ce script à partir).
Cependant, bash
gère ce SIGINT de manière asynchrone, uniquement après la fin de la commande en cours d'exécution. bash
sort uniquement à 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
Au- dessus, bash
, sh
et sleep
recevoir SIGINT quand j'appuyez sur Ctrl-C, mais les sh
sorties normalement avec un code de sortie 0, donc bash
ne tient pas compte du SIGINT, ce qui est la raison pour laquelle nous voyons « ici ».
ping
, du moins celui d’iputils, se comporte comme ça. Lorsqu'il est interrompu, il imprime des statistiques et quitte avec un état de sortie égal à 0 ou 1, selon que ses pings ont été répondus ou non. Ainsi, lorsque vous appuyez sur Ctrl-C en ping
cours d’exécution, bash
vous notez que vous avez appuyé Ctrl-C
dans ses gestionnaires SIGINT, mais que ping
, normalement, il bash
ne se ferme pas.
Si vous ajoutez une sleep 1
boucle dans cette boucle et appuyez sur tant Ctrl-C
que vous êtes en sleep
cours d'exécution, car sleep
il n'y a pas de gestionnaire spécial sur SIGINT, il mourra et signalera bash
qu'il est mort d'un SIGINT et, dans ce cas, il se bash
fermera (il se tuera lui-même avec SIGINT afin signaler l'interruption à son parent).
En ce qui concerne pourquoi bash
se comporte comme ça, je ne suis pas sûr et je remarque que le comportement n'est pas toujours déterministe. Je viens de poser la question sur la bash
liste de diffusion de développement ( Mise à jour : @Jilles a maintenant précisé la raison dans sa réponse ).
Le seul autre shell que j'ai trouvé qui se comporte de manière similaire est ksh93 (Update, comme mentionné par @Jilles, FreeBSD égalementsh
). 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'
Ne pas sortir "test". C'est-à-dire qu'il se ferme (en se tuant avec SIGINT là) si la commande qu'elle attendait meurt de SIGINT, même si elle-même n'a pas reçu ce SIGINT.
Une solution consiste à ajouter un:
trap 'exit 130' INT
En haut du script pour forcer la bash
sortie à la réception d'un SIGINT (notez que dans tous les cas, le SIGINT ne sera pas traité de manière synchrone, uniquement après la fin de la commande en cours d'exécution).
Idéalement, nous aimerions signaler à notre parent que nous sommes morts d'un SIGINT (afin que, s'il s'agit d'un autre bash
script par exemple, ce bash
script soit également interrompu). Faire un exit 130
n'est pas la même chose que mourir de SIGINT (bien que certains obus aient la $?
même valeur dans 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).
Toutefois , pour bash
, ksh93
ou FreeBSD sh
, cela ne fonctionne pas. Le statut de sortie 130 n'est pas considéré comme une mort par SIGINT et un script parent ne serait pas abandonné là.
Donc, une meilleure alternative serait de se tuer avec SIGINT après avoir reçu SIGINT:
trap '
trap - INT # restore default INT handler
kill -s INT "$$"
' INT
for f in *.txt; do vi "$f"; cp "$f" newdir; done
. Si l'utilisateur tape Ctrl + C lors de la modification de l'un des fichiers,vi
affiche simplement un message. Il semble raisonnable que la boucle se poursuive une fois que l'utilisateur a fini de modifier le fichier. (Et oui, je sais que vous pourriez direvi *.txt; cp *.txt newdir
; je soumets simplement lafor
boucle à titre d'exemple.)vi
(vim
du moins, au moins) désactive ttyisig
lors de l'édition (cela ne se fait pas de manière évidente lorsque vous exécutez:!cmd
, et cela s'appliquerait beaucoup dans ce cas).ping
quitte avec 0 après avoir reçu SIGINT. J'ai trouvé un comportement similaire quand un script bash contientsudo
au lieu deping
, mais sesudo
ferme avec 1 après avoir reçu SIGINT. unix.stackexchange.com/questions/479023/…L'explication est que bash implémente WCE (attente et sortie coopérative) pour SIGINT et SIGQUIT selon http://www.cons.org/cracauer/sigint.html . Cela signifie que si bash reçoit SIGINT ou SIGQUIT en attendant la fin d'un processus, il attendra que le processus se termine et se termine lui-même si le processus s'est arrêté sur ce signal. Cela garantit que les programmes qui utilisent SIGINT ou SIGQUIT dans leur interface utilisateur fonctionneront comme prévu (si le signal ne provoque pas la fin du programme, le script continue normalement).
Un inconvénient apparaît avec les programmes qui interceptent SIGINT ou SIGQUIT mais s’arrêtent à cause de cela, mais en utilisant une sortie normale () au lieu de renvoyer le signal à eux-mêmes. Il peut ne pas être possible d'interrompre les scripts qui appellent de tels programmes. Je pense que la solution réelle existe dans de tels programmes tels que ping et ping6.
Un comportement similaire est mis en œuvre par ksh93 et par / bin / sh de FreeBSD, mais pas par la plupart des autres shells.
la source
exit(130)
par exemple une interruption si vous l'interrompezmksh -c 'sleep 10;:'
).Comme vous le supposez, cela est dû au fait que SIGINT a été envoyé au processus subordonné et que le shell continue après la fin de ce processus.
Pour mieux gérer cela, vous pouvez vérifier le statut de sortie des commandes en cours d'exécution. Le code de retour Unix code à la fois la méthode par laquelle un processus s'est
exit()
terminé (appel système ou signal) et la valeur à laquelle le processus a été passé ou quel signal a mis fin au processus. Tout cela est assez compliqué, mais le moyen le plus rapide de l’utiliser est de savoir qu’un processus terminé par signal aura un code retour non nul. Ainsi, si vous vérifiez le code de retour dans votre script, vous pouvez quitter vous-même si le processus enfant a été arrêté, éliminant ainsi le besoin d'inélégations comme lessleep
appels inutiles . Un moyen rapide de le faire tout au long de votre script consiste à utiliserset -e
, bien que cela puisse nécessiter quelques ajustements pour les commandes dont le statut de sortie est différent de zéro.la source
ping
renvoie avec un état de sortie 0 à la réception de SIGINT et qu’ilbash
ignore ensuite le SIGINT qu’il a reçu lui-même si tel est le cas. Ajouter un "set -e" ou vérifier l'état de sortie ne vous aidera pas ici. Ajouter un piège explicite sur SIGINT serait utile.Le terminal remarque le control-c et envoie un
INT
signal au groupe de processus de premier plan, qui inclut ici le shell, car ilping
n'a pas créé de nouveau groupe de processus de premier plan. Ceci est facile à vérifier en piégeantINT
.Si la commande en cours d'exécution a créé un nouveau groupe de processus de premier plan, alors le control-c ira à ce groupe de processus et non au shell. Dans ce cas, le shell devra inspecter les codes de sortie, car le terminal ne le signalera pas.
(
INT
Manutention dans des coquilles peut être compliqué fabuleusement, par ailleurs, que la coquille a parfois besoin d'ignorer le signal, et parfois pas plongée Source si curieux, ou Ponder.tail -f /etc/passwd; echo foo
)la source
ping
n'a pas de raison de démarrer un nouveau groupe de processus ici et la version de ping (iputils sur Debian) avec laquelle je peux reproduire le problème de l'OP ne crée pas de groupe de processus.Eh bien, j'ai essayé d'ajouter un
sleep 1
au script bash, et bang!Maintenant, je suis capable de l'arrêter avec deux Ctrl+C.
En appuyant sur Ctrl+C, un
SIGINT
signal est envoyé au processus en cours d'exécution, quelle commande a été exécutée à l'intérieur de la boucle. Ensuite, le processus de sous - shell continue à exécuter la commande suivante dans la boucle, ce qui démarre un autre processus. Pour pouvoir arrêter le script, il est nécessaire d’envoyer deuxSIGINT
signaux, un pour interrompre la commande en cours d’exécution et un pour interrompre le processus de sous - shell .Dans le script sans
sleep
appel, une pression Ctrl+Ctrès rapide et répétée ne semble pas fonctionner et il est impossible de sortir de la boucle. J'imagine qu'appuyer deux fois n'est pas assez rapide pour arriver juste au bon moment entre l'interruption du processus en cours d'exécution et le début du processus suivant. Chaque Ctrl+Cpression sur envoie un messageSIGINT
à un processus exécuté à l'intérieur de la boucle, mais non plus au sous - shell .Dans le script avec
sleep 1
, cet appel suspend l'exécution pendant une seconde et, lorsqu'il est interrompu par le premier Ctrl+C(premierSIGINT
), l' exécution de la commande suivante par le sous - shell prendra plus de temps. Alors maintenant, la seconde Ctrl+C(secondeSIGINT
) ira au sous - shell et l'exécution du script se terminera.la source
Essaye ça:
Maintenant changez la première ligne en:
et essayez à nouveau - voyez si le ping est maintenant interruptible.
la source
par exemple
pgrep -f firefox
, grep le PID de l'exécutionfirefox
et sauvegardera ce PID dans un fichier appeléany_file_name
. La commande 'sed' ajoutera lekill
début du numéro PID dans le fichier 'any_file_name'. La troisième ligneany_file_name
classera l'exécutable. La quatrième ligne supprimera le PID disponible dans le fichierany_file_name
. L'écriture des quatre lignes ci-dessus dans un fichier et l'exécution de ce fichier peuvent effectuer l'opération Control- C. Travailler absolument bien pour moi.la source
Si quelqu'un cherche un correctif pour cette
bash
fonctionnalité et pas tant la philosophie qui la sous-tend , voici une proposition:N'exécutez pas la commande problématique directement, mais à partir d'un wrapper qui a) attend qu'il se termine b) ne gêne pas les signaux et c) n'implémente pas le mécanisme WCE, mais meurt simplement à la réception d'un
SIGINT
.Un tel emballage pourrait être fait avec
awk
+ sasystem()
fonction.Mettez dans un script comme OP:
la source
Vous êtes victime d'un bogue bien connu. Bash fait jobcontrol pour les scripts, ce qui est une erreur.
En réalité, bash exécute les programmes externes dans un groupe de processus différent de celui utilisé pour le script lui-même. Comme le groupe de processus TTY est défini sur le groupe de processus du processus de premier plan actuel, seul ce processus de premier plan est supprimé et la boucle dans le script shell continue.
Pour vérifier: Extrayez et compilez un Bourne Shell récent qui implémente pgrp (1) en tant que programme intégré, puis ajoutez un / bin / sleep 100 (ou / usr / bin / sleep selon votre plate-forme) à la boucle de script, puis démarrez le processus. Bourne Shell. Après avoir utilisé ps (1) pour obtenir les ID de processus pour la commande de veille et le bash qui exécute le script, appelez
pgrp <pid>
et remplacez "<pid>" par l'ID de processus des veille et bash qui exécutent le script. Vous verrez différents ID de groupe de processus. Appelez maintenant quelque chose commepgrp < /dev/pts/7
(remplacez le nom de tty par le tty utilisé par le script) pour obtenir le groupe de processus tty actuel. Le groupe de processus TTY est égal au groupe de processus de la commande de veille.Pour réparer: utilisez un shell différent.
Les sources récentes de Bourne Shell se trouvent dans mon paquet d’outils schily que vous pouvez trouver ici:
http://sourceforge.net/projects/schilytools/files/
la source
bash
s'agit-il? Autantbash
que je sache, cela n’est possible que si vous passez l’option -m ou -i.bash -c 'ps -j; ps -j; ps -j'
)./bin/sh -ce
. J'ai dû ajouter une solution de contournement mochesmake
qui tue explicitement le groupe de processus d'une commande en cours d'exécution afin d'autoriser l'^C
abandon d'un appel de création en couches. Avez-vous vérifié si bash a modifié le groupe de processus à partir de l'ID de groupe de processus avec lequel il a été lancé?ARGV0=sh bash -ce 'ps -j; ps -j; ps -j'
rapporte le même pgid pour ps et bash dans toutes les invocations de 3 ps. (ARGV0 = sh est lezsh
moyen de passer argv [0]).