Exécuter plusieurs commandes et les tuer comme une seule à bash

52

Je veux exécuter plusieurs commandes (processus) sur un seul shell. Tous ont leur propre sortie continue et ne s'arrêtent pas. Les exécuter dans les pauses de fond Ctrl- C. Je voudrais les exécuter en tant que processus unique (sous-shell, peut-être?) Pour pouvoir les arrêter tous avec Ctrl- C.

Pour être plus précis, je veux exécuter des tests unitaires avec mocha(mode surveillance), exécuter le serveur et exécuter un prétraitement de fichier (mode surveillance) et voir la sortie de chacun dans une fenêtre de terminal. En gros, je veux éviter d’utiliser certains coureurs de tâches.

Je peux le réaliser en exécutant des processus en arrière-plan ( &), mais je dois ensuite les mettre au premier plan pour les arrêter. J'aimerais avoir un processus pour les envelopper et quand j'arrête le processus, il arrête ses "enfants".

utilisateur1876909
la source
Devraient-ils fonctionner simultanément ou l'un après l'autre?
Minix
Oui, les processus doivent être exécutés simultanément, mais j'ai besoin de voir les résultats de chacun.
user1876909

Réponses:

67

Pour exécuter des commandes simultanément, vous pouvez utiliser le &séparateur de commandes.

~$ command1 & command2 & command3

Cela va commencer command1, puis l'exécute en arrière-plan. La même chose avec command2. Ensuite, ça commence command3normalement.

Le résultat de toutes les commandes sera brouillé ensemble, mais si cela ne vous pose pas de problème, ce serait la solution.

Si vous souhaitez examiner ultérieurement la sortie séparément, vous pouvez diriger la sortie de chaque commande vers celle-ci tee, ce qui vous permet de spécifier un fichier dans lequel la sortie doit être mise en miroir.

~$ command1 | tee 1.log & command2 | tee 2.log & command3 | tee 3.log

La sortie sera probablement très compliquée. Pour contrer cela, vous pouvez attribuer un préfixe à la sortie de chaque commande sed.

~$ echo 'Output of command 1' | sed -e 's/^/[Command1] /' 
[Command1] Output of command 1

Donc, si nous mettons tout cela ensemble, nous obtenons:

~$ command1 | tee 1.log | sed -e 's/^/[Command1] /' & command2 | tee 2.log | sed -e 's/^/[Command2] /' & command3 | tee 3.log | sed -e 's/^/[Command3] /'
[Command1] Starting command1
[Command2] Starting command2
[Command1] Finished
[Command3] Starting command3

Ceci est une version très idéalisée de ce que vous allez probablement voir. Mais c'est le meilleur que je puisse penser en ce moment.

Si vous voulez les arrêter tous en même temps, vous pouvez utiliser le build in trap.

~$ trap 'kill %1; kill %2' SIGINT
~$ command1 & command2 & command3

Cela va s’exécuter command1et command2à l’arrière-plan et command3au premier plan, ce qui vous permet de le tuer avec Ctrl+ C.

Lorsque vous tuez le dernier processus avec Ctrl+, Cles kill %1; kill %2commandes sont exécutées, car nous avons connecté leur exécution à la réception d'un SIGnal INTERupt, ce qui est envoyé en appuyant sur Ctrl+ C.

Ils tuent respectivement le premier et le deuxième processus en arrière-plan (votre command1et command2). N'oubliez pas de retirer le piège après avoir utilisé vos commandes trap - SIGINT.

Terminer le monstre d'une commande:

~$ trap 'kill %1; kill %2' SIGINT
~$ command1 | tee 1.log | sed -e 's/^/[Command1] /' & command2 | tee 2.log | sed -e 's/^/[Command2] /' & command3 | tee 3.log | sed -e 's/^/[Command3] /'

Vous pouvez bien sûr regarder l' écran . Il vous permet de diviser votre console en autant de consoles distinctes que vous le souhaitez. Vous pouvez donc surveiller toutes les commandes séparément, mais en même temps.

Minix
la source
3
Dans l'attente de toutes les critiques. :)
Minix
2
Je vous remercie. Il fait exactement ce que je voulais (la commande trap). La solution doit cependant être encapsulée dans un script pour pouvoir être réutilisée.
user1876909
@ user1876909 Voici un squelette. Les détails sont à vous [1]. Heureux d'avoir pu aider. [1] pastebin.com/jKhtwF40
Minix
3
c'est une solution assez intelligente, iv a appris quelque chose de nouveau, bravo +1
mike-m
1
Ceci est incroyable. J'ai beaucoup à creuser et à apprendre de cette réponse.
ccnokes
20

Vous pouvez facilement tuer un ensemble de processus à la fois si vous organisez leur exécution (et uniquement eux) dans le même groupe de processus .

Linux fournit l'utilitaire setsidpour exécuter un programme dans un nouveau groupe de processus (même dans une nouvelle session, mais cela ne nous dérange pas). (Ceci est faisable mais plus compliqué sans setsid.)

L'ID de groupe de processus (PGID) d'un groupe de processus est l'ID de processus du processus parent d'origine dans le groupe. Pour supprimer tous les processus d'un groupe de processus, transmettez le négatif du PGID à l' killappel système ou à la commande. Le PGID reste valide même si le processus original avec ce PID est mort (bien que cela puisse être un peu déroutant).

setsid sh -c 'command1 & command2 & command3' &
pgid=$!
echo "Background tasks are running in process group $pgid, kill with kill -TERM -$pgid"

Si vous exécutez les processus en arrière-plan à partir d'un shell non interactif , ils resteront tous dans le groupe de processus du shell. Ce n'est que dans les shells interactifs que les processus d'arrière-plan s'exécutent dans leur propre groupe de processus. Par conséquent, si vous branchez les commandes d'un shell non interactif qui reste au premier plan, Ctrl+ Cles supprimera toutes. Utilisez la commande waitintégrée pour que le shell attende la fermeture de toutes les commandes.

sh -c 'command1 & command2 & command3 & wait'
# Press Ctrl+C to kill them all
Gilles, arrête de faire le mal
la source
2
réponse non déduite (la méthode en bas). Simple à comprendre et à mémoriser.
Nicolas Marshall
14

Je suis surpris que ce ne soit pas encore arrivé, mais ma méthode préférée consiste à utiliser des sous-shell avec des commandes d'arrière-plan. Usage:

(command1 & command2 & command3)

La sortie est visible des deux côtés, toutes les commandes et commodités de bash sont toujours disponibles, et une seule les Ctrl-ctue toutes. J'utilise généralement cela pour démarrer un serveur de fichiers client à débogage actif et un serveur principal en même temps, afin de pouvoir les tuer facilement lorsque je le souhaite.

La façon dont cela fonctionne est la suivante command1et command2s’exécute à l’arrière-plan d’une nouvelle instance de sous-shell, elle command3est au premier plan de cette instance et le sous-shell reçoit en premier le signal de Ctrl-cdésactivation d’ une touche. Cela ressemble beaucoup à l'exécution d'un script bash en ce qui concerne le propriétaire de quel processus et le moment où les programmes en arrière-plan sont supprimés.

jv-dev
la source
Si vous ajoutez waitla commande finale (commande3 dans votre exemple), vous pouvez exécuter le code de nettoyage après que l'utilisateur a appuyé sur Ctrl-c.
dotnetCarpenter
0

Vous pouvez utiliser le point-virgule ;ou &&comme ceci:

cmd1; cmd2   # Runs both the commands, even if the first one exits with a non-zero status.

cmd1 && cmd2 # Only runs the second command if the first one was successful.
Serenesat
la source
Cela n'exécute pas les commandes simultanément.
Gilles, arrête de faire le mal
-2

Vous pouvez utiliser un pipe. Cela démarrera les commandes aux deux extrémités du tuyau en même temps. Vous devriez pouvoir arrêter toutes les commandes avec le CTRL-Caussi.

1st command | 2nd command | 3rd command

Si vous voulez exécuter les commandes les unes après les autres, vous pouvez utiliser la méthode de @ serenesat.

Sree
la source
Cela transmet la sortie de la première commande à la seconde (et ainsi de suite), ce qui n’est pas approprié ici car la sortie de la première commande est supposée aboutir sur le terminal.
Gilles, arrête de faire le mal