Pourquoi ne puis-je pas tuer un timeout d'attente appelé à partir d'un script Bash avec une frappe?

[Edit: Cela ressemble à d'autres questions demandant comment tuer tous les process engendrés – les réponses semblent toutes être d'utiliser pkill. Le kernel de ma question peut donc être: Existe-t-il un moyen de propager Ctrl-C / Z à tous les process générés par un script?]

Lors de l'appel d'un SoX rec avec la command timeout de coreutils (discuté ici ), il ne semble pas possible de le tuer avec une frappe une fois qu'il a été appelé à partir d'un script Bash.

Exemples:

 timeout 10 rec test.wav 

… peut être tué avec Ctrl + C ou Ctrl + Z depuis bash, mais pas quand il a été appelé à partir d'un script.

 timeout 10 ping nowhere 

… peut être tué avec Ctrl + C ou Ctrl + Z de bash, et avec Ctrl + Z quand il est exécuté à l'intérieur d'un script.

Je peux find l'ID de process et le tuer de cette façon, mais pourquoi ne puis-je pas utiliser une touche de raccourci standard? Et y a-t-il un moyen de structurer mon script pour pouvoir le faire?

Les touches de signal telles que Ctrl + C envoient un signal à tous les process du groupe de process de premier plan.

Dans le cas typique, un groupe de process est un pipeline. Par exemple, dans la head <somefile | sort head <somefile | sort , le process running head et le process running sort sont dans le même groupe de process, tout comme le shell, afin qu'ils reçoivent tous le signal. Lorsque vous exécutez un travail en arrière-plan ( somecommand & ), ce travail est dans son propre groupe de process, donc appuyer sur Ctrl + C ne l'affecte pas.

Le programme de timeout place dans son propre groupe de process. Du code source:

 /* Ensure we're in our own group so all subprocesses can be killed. Note we don't just put the child in a separate group as then we would need to worry about foreground and background groups and propagating signals between them. */ setpgid (0, 0); 

Lorsqu'un timeout d'expiration se produit, le timeout passe par le simple moyen de tuer le groupe de process dont il est membre. Comme il s'est placé dans un groupe de process distinct, son process parent ne sera pas dans le groupe. L'utilisation d'un groupe de process garantit que si l'application enfant se connecte à plusieurs process, tous les process recevront le signal.

Lorsque vous exécutez le timeout directement sur la command line et que vous appuyez sur Ctrl + C , le SIGINT résultant est reçu par timeout et par le process enfant, mais pas par le shell interactif qui est le process parent de timeout . Lorsque le timeout est appelé à partir d'un script, seul le shell exécutant le script reçoit le signal: le timeout ne l'obtient pas car il se trouve dans un autre groupe de process.

Vous pouvez définir un gestionnaire de signal dans un script shell avec le trap embedded. Malheureusement, ce n'est pas si simple. Considère ceci:

 #!/bin/sh trap 'echo Interrupted at $(date)' INT date timeout 5 sleep 10 date 

Si vous appuyez sur Ctrl + C après 2 secondes, cela attend encore les 5 secondes complètes, puis imprimez le message "Interrompu". C'est parce que le shell s'abstient d'exécuter le code de trap pendant qu'un travail de premier plan est actif.

Pour remédier à cela, exécutez le travail en arrière-plan. Dans le gestionnaire de signaux, appelez kill pour relayer le signal vers le groupe de process de timeout .

 #!/bin/sh trap 'kill -INT -$pid' INT timeout 5 sleep 10 & pid=$! wait $pid 

S'appuyant sur l'excellente réponse fournie par Gilles. La command timeout a une option de premier plan, si elle est utilisée, alors une CTRL + C quittera la command timeout.

 #!/bin/sh trap 'echo caught interrupt and exiting;exit' INT date timeout --foreground 5 sleep 10 date 

Je pense que vous ne comprenez pas ce qui se passe ici.

Fondamentalement, Ctrl + C envoie un signal SIGINT, tandis que Ctrl + Z envoie un signal SIGTSTP.

SIGTSTP arrête juste le process – un SIGCONT le poursuivra.

Cela fonctionne sur le process forground qui a été forké sur la command line.

Si votre process est un process d'arrière-plan, vous devrez envoyer ce signal aux process d'une manière différente. kill fera ça. En théorie, un opérateur «-» sur ce kill devrait également signaler les process enfants, mais cela fonctionne rarement comme prévu.

Pour en savoir plus: Unix-Signals