Führe mehrere Befehle aus und töte sie als einen in bash

51

Ich möchte mehrere Befehle (Prozesse) auf einer einzigen Shell ausführen. Alle von ihnen haben eine eigene kontinuierliche Ausgabe und hören nicht auf. Laufen sie im Hintergrund bricht Ctrl- C. Ich möchte sie als einen einzigen Prozess ausführen (Subshell, vielleicht?), Um alle mit Ctrl- stoppen zu können C.

Um genau zu sein, möchte ich Komponententests mit mocha(Überwachungsmodus) ausführen, einen Server ausführen und eine Dateivorbearbeitung (Überwachungsmodus) ausführen und die Ausgabe von jedem in einem Terminalfenster sehen. Grundsätzlich möchte ich vermeiden, einen Task-Runner zu verwenden.

Ich kann es realisieren, indem ich Prozesse im Hintergrund laufen lasse ( &), aber dann muss ich sie in den Vordergrund stellen, um sie zu stoppen. Ich hätte gerne einen Prozess, um sie zu verpacken, und wenn ich den Prozess stoppe, werden seine "Kinder" gestoppt.

user1876909
quelle
Sollten sie gleichzeitig oder nacheinander ausgeführt werden?
Minix
Ja, Prozesse sollten gleichzeitig ausgeführt werden, aber ich muss die Ausgabe von jedem sehen.
user1876909

Antworten:

67

Um Befehle gleichzeitig auszuführen, können Sie das &Befehlstrennzeichen verwenden.

~$ command1 & command2 & command3

Dies wird gestartet command1und dann im Hintergrund ausgeführt. Das selbe mit command2. Dann geht es command3normal los.

Die Ausgabe aller Befehle wird unkenntlich gemacht. Wenn dies für Sie jedoch kein Problem darstellt, ist dies die Lösung.

Wenn Sie später einen separaten Blick auf die Ausgabe werfen möchten, können Sie die Ausgabe jedes Befehls weiterleiten tee. Auf diese Weise können Sie eine Datei angeben, in die die Ausgabe gespiegelt werden soll.

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

Die Ausgabe wird wahrscheinlich sehr chaotisch sein. Um dem entgegenzuwirken, können Sie die Ausgabe jedes Befehls mit einem Präfix versehen sed.

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

Wenn wir all das zusammenfassen, erhalten wir:

~$ 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

Dies ist eine sehr idealisierte Version dessen, was Sie wahrscheinlich sehen werden. Aber es ist das Beste, was mir derzeit einfällt.

Wenn Sie alle auf einmal stoppen möchten, können Sie das eingebaute Programm verwenden trap.

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

Dies wird ausgeführt , command1und command2im Hintergrund und command3im Vordergrund, mit dem Sie es mit läßt töten Ctrl+ C.

Wenn Sie den letzten Vorgang mit töten Ctrl+ Cdie kill %1; kill %2Befehle ausgeführt werden , weil wir ihre Ausführung im Zusammenhang mit der Aufnahme eines INTERUPT Signal, das Ding durch Drücken geschickt Ctrl+ C.

Sie beenden jeweils den 1. und 2. Hintergrundprozess (Ihr command1und command2). Vergessen Sie nicht, die Falle zu entfernen, nachdem Sie Ihre Befehle mit beendet haben trap - SIGINT.

Schließe das Monster eines Befehls ab:

~$ 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] /'

Sie könnten natürlich auch einen Blick auf den Bildschirm werfen . Damit können Sie Ihre Konsole in beliebig viele separate Konsolen aufteilen. So können Sie alle Befehle einzeln, aber gleichzeitig überwachen.

Minix
quelle
3
Freue mich auf alle Kritik. :)
Minix
2
Danke. Es macht genau das, was ich wollte (der Trap-Befehl). Die Lösung muss jedoch zur Wiederverwendbarkeit in ein Skript eingebunden werden.
user1876909
@ user1876909 Hier ist ein Skelett. Die Einzelheiten liegen bei Ihnen [1]. Froh, dass ich helfen konnte. [1] pastebin.com/jKhtwF40
Minix
3
das ist eine ziemlich clevere lösung, ich habe etwas neues gelernt, bravo +1
mike-m
1
Das ist großartig. Ich muss mich mit dieser Antwort beschäftigen und daraus lernen.
Ccnokes
20

Sie können problemlos mehrere Prozesse gleichzeitig beenden, wenn Sie festlegen, dass sie (und nur sie) in derselben Prozessgruppe ausgeführt werden .

Linux bietet das Dienstprogramm setsidzum Ausführen eines Programms in einer neuen Prozessgruppe (auch in einer neuen Sitzung, aber das interessiert uns nicht). (Dies ist machbar, aber ohne kompliziertersetsid .)

Die Prozessgruppen-ID (PGID) einer Prozessgruppe ist die Prozess-ID des ursprünglichen übergeordneten Prozesses in der Gruppe. Übergeben Sie das Negativ der PGID an den killSystemaufruf oder Befehl, um alle Prozesse in einer Prozessgruppe abzubrechen . Die PGID bleibt auch dann gültig, wenn der ursprüngliche Prozess mit dieser PID abbricht (obwohl dies etwas verwirrend sein kann).

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

Wenn Sie die Prozesse im Hintergrund von einer nicht interaktiven Shell ausführen , verbleiben sie alle in der Prozessgruppe der Shell. Nur in interaktiven Shells werden Hintergrundprozesse in einer eigenen Prozessgruppe ausgeführt. Wenn Sie also die Befehle aus einer nicht interaktiven Shell herauslösen, die im Vordergrund bleibt, werden sie alle mit Ctrl+ beendet C. Verwenden Sie die integrierte waitFunktion, um die Shell auf das Beenden aller Befehle warten zu lassen.

sh -c 'command1 & command2 & command3 & wait'
# Press Ctrl+C to kill them all
Gilles 'SO - hör auf böse zu sein'
quelle
2
unterschätzte Antwort (die Methode unten). Einfach zu verstehen und auswendig zu lernen.
Nicolas Marshall
14

Ich bin überrascht, dass dies noch nicht der Fall ist, aber meine Lieblingsmethode ist die Verwendung von Subshells mit Hintergrundbefehlen. Verwendungszweck:

(command1 & command2 & command3)

Die Ausgabe ist von beiden sichtbar, alle Bash-Befehle und -Befehle sind weiterhin verfügbar, und ein einziger Ctrl-cBefehl beendet sie alle. Normalerweise starte ich damit gleichzeitig einen Live-Debugging-Client-Dateiserver und einen Back-End-Server, sodass ich beide problemlos beenden kann, wenn ich möchte.

Die Art und Weise dies funktioniert , ist , dass command1und command2im Hintergrund einer neuen Sub - Shell - Instanz ausgeführt wird , und command3steht im Vordergrund dieser Instanz, und die Sub - Shell ist , was zuerst das Kill - Signal von einem erhält Ctrl-ckeypress. Es ist fast so, als würde man ein Bash-Skript ausführen, um herauszufinden, wem welcher Prozess gehört und wann Hintergrundprogramme beendet werden.

jv-dev
quelle
Wenn Sie waitals letzten Befehl (Befehl3 in Ihrem Beispiel) hinzufügen , können Sie Bereinigungscode ausführen , nachdem der Benutzer Strg-C gedrückt hat .
DotnetCarpenter
0

Sie können das Semikolon verwenden ;oder folgendermaßen &&vorgehen:

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
quelle
Dadurch werden die Befehle nicht gleichzeitig ausgeführt.
Gilles 'SO- hör auf böse zu sein'
-2

Sie können a verwenden pipe. Dadurch werden die Befehle an beiden Enden der Leitung gleichzeitig gestartet. Sie sollten auch in der Lage sein, alle Befehle zu stoppen CTRL-C.

1st command | 2nd command | 3rd command

Wenn Sie die Befehle nacheinander ausführen möchten, können Sie die @ serenesat-Methode verwenden.

Sree
quelle
Dies übergibt die Ausgabe des ersten Befehls an den zweiten (und so weiter), was hier nicht angemessen ist, da die Ausgabe des ersten Befehls auf dem Terminal enden soll.
Gilles 'SO- hör auf böse zu sein'