Beenden Sie bei zwei gegebenen Hintergrundbefehlen den verbleibenden, wenn einer von beiden beendet wird

14

Ich habe ein einfaches Bash-Skript, das zwei Server startet:

#!/bin/bash
(cd ./frontend && gulp serve) & (cd ./backend && gulp serve --verbose)

Wenn der zweite Befehl beendet wird, scheint der erste Befehl weiterhin ausgeführt zu werden.

Wie kann ich dies ändern, damit der andere Befehl beendet wird, wenn einer der Befehle beendet wird?

Beachten Sie, dass die Fehlerstufen der Hintergrundprozesse nicht überprüft werden müssen, sondern nur, ob sie beendet wurden.

blah238
quelle
Warum nicht gulp ./fronend/serve && gulp ./backend/serve --verbose?
Heemayl
serveIst ein Argument, keine Datei, daher muss das aktuelle Verzeichnis festgelegt werden.
blah238,
1
Dies sind auch lang laufende Prozesse, die gleichzeitig ausgeführt werden müssen, sorry, wenn das nicht klar war.
blah238

Antworten:

21

Dies startet beide Prozesse, wartet auf den ersten, der beendet wird und beendet dann den anderen:

#!/bin/bash
{ cd ./frontend && gulp serve; } &
{ cd ./backend && gulp serve --verbose; } &
wait -n
pkill -P $$

Wie es funktioniert

  1. Start:

    { cd ./frontend && gulp serve; } &
    { cd ./backend && gulp serve --verbose; } &

    Die beiden obigen Befehle starten beide Prozesse im Hintergrund.

  2. Warten

    wait -n

    Dies wartet darauf, dass einer der Hintergrundjobs beendet wird.

    Aufgrund der -nOption ist hierfür mindestens Bash 4.3 erforderlich.

  3. Töten

    pkill -P $$

    Dadurch werden alle Jobs beendet, bei denen der aktuelle Prozess der übergeordnete Prozess ist. Mit anderen Worten, dies beendet jeden Hintergrundprozess, der noch ausgeführt wird.

    Wenn Ihr System nicht über pkillFolgendes verfügt, ersetzen Sie diese Zeile durch:

    kill 0

    die auch tötet die aktuelle Prozessgruppe .

Leicht testbares Beispiel

Indem wir das Skript ändern, können wir es auch ohne gulpInstallation testen :

$ cat script.sh 
#!/bin/bash
{ sleep $1; echo one;  } &
{ sleep $2; echo two;  } &
wait -n
pkill -P $$
echo done

Das obige Skript kann ausgeführt werden als bash script.sh 1 3und der erste Prozess wird zuerst beendet. Alternativ kann man es als ausführen bash script.sh 3 1und der zweite Prozess wird zuerst beendet. In beiden Fällen kann man sehen, dass dies wie gewünscht funktioniert.

John1024
quelle
Das sieht gut aus. Leider funktionieren diese Befehle in meiner Bash-Umgebung (msysgit) nicht. Das ist mein Fehler, wenn ich das nicht spezifiziere. Ich werde es aber auf einer echten Linux-Box ausprobieren.
blah238,
(1) Nicht alle Versionen bashunterstützen die -nOption zum waitBefehl. (2) Ich stimme dem ersten Satz zu 100% zu - Ihre Lösung startet zwei Prozesse, wartet, bis der erste beendet ist, und tötet dann den anderen. Aber die Frage lautet: "... wenn einer der Befehle fehlerfrei ist, wird der andere beendet?" Ich glaube, dass Ihre Lösung nicht den Anforderungen des OP entspricht. (3) Warum hast du gewechselt (…) &zu { …; } &? Die &Kräfte der Liste (Gruppenkommando) ohnehin in einer Subshell auszuführen. IMHO, Sie haben Zeichen hinzugefügt und möglicherweise Verwirrung (ich musste es zweimal anschauen, um es zu verstehen) ohne Nutzen eingeführt.
G-Man sagt, dass Monica
1
John hat recht, es handelt sich um Webserver, die normalerweise beide ausgeführt werden sollten, es sei denn, ein Fehler tritt auf oder sie werden zum Beenden aufgefordert. Ich glaube also nicht, dass wir die Fehlerstufe jedes Prozesses überprüfen müssen, sondern nur, ob er noch ausgeführt wird.
blah238
2
pkillist für mich nicht verfügbar, kill 0scheint aber den gleichen Effekt zu haben. Außerdem habe ich meine Git für Windows-Umgebung aktualisiert und es sieht so aus, als ob es wait -njetzt funktioniert. Daher akzeptiere ich diese Antwort.
blah238,
1
Interessant. Obwohl ich sehe, dass dieses Verhalten für einige Versionen von dokumentiert ist kill. Die Dokumentation auf meinem System erwähnt es nicht. Funktioniert aber kill 0trotzdem. Guter Fund!
John1024
1

Das ist knifflig. Folgendes habe ich mir ausgedacht: es ist möglicherweise möglich, es zu vereinfachen / zu rationalisieren:

#!/bin/sh

pid1file=$(mktemp)
pid2file=$(mktemp)
stat1file=$(mktemp)
stat2file=$(mktemp)

while true; do sleep 42; done &
main_sleeper=$!

(cd frontend && gulp serve           & echo "$!" > "$pid1file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat1file"; kill "$main_sleeper" 2> /dev/null) &
(cd backend  && gulp serve --verbose & echo "$!" > "$pid2file";
    wait "$!" 2> /dev/null; echo "$?" > "$stat2file"; kill "$main_sleeper" 2> /dev/null) &
sleep 1
wait "$main_sleeper" 2> /dev/null

if stat1=$(<"$stat1file")  &&  [ "$stat1" != "" ]  &&  [ "$stat1" != 0 ]
then
        echo "First process failed ..."
        if pid2=$(<"$pid2file")  &&  [ "$pid2" != "" ]
        then
                echo "... killing second process."
                kill "$pid2" 2> /dev/null
        fi
fi
if [ "$stat1" = "" ]  &&  \
   stat2=$(<"$stat2file")  &&  [ "$stat2" != "" ]  &&  [ "$stat2" != 0 ]
then
        echo "Second process failed ..."
        if pid1=$(<"$pid1file")  &&  [ "$pid1" != "" ]
        then
                echo "... killing first process."
                kill "$pid1" 2> /dev/null
        fi
fi

wait
if stat1=$(<"$stat1file")
then
        echo "Process 1 terminated with status $stat1."
else
        echo "Problem getting status of process 1."
fi
if stat2=$(<"$stat2file")
then
        echo "Process 2 terminated with status $stat2."
else
        echo "Problem getting status of process 2."
fi
  • Starten Sie zuerst einen Prozess ( while true; do sleep 42; done &), der für immer schläft / pausiert. Wenn Sie sicher sind, dass Ihre beiden Befehle innerhalb einer bestimmten Zeitspanne (z. B. einer Stunde) beendet werden, können Sie dies in einen einzelnen Ruhezustand ändern, der diese Zeitspanne überschreitet (z sleep 3600. B. ). Sie können dann die folgende Logik ändern, um diese als Zeitüberschreitung zu verwenden. dh, beenden Sie die Prozesse, wenn sie nach dieser Zeit noch ausgeführt werden. (Beachten Sie, dass das obige Skript dies derzeit nicht tut.)
  • Starten Sie die beiden asynchronen (gleichzeitigen Hintergrund-) Prozesse.
    • Sie brauchen nicht ./für cd.
    • command & echo "$!" > somewhere; wait "$!" ist ein kniffliges Konstrukt, das einen Prozess asynchron startet, seine PID erfasst und dann darauf wartet. es zu einer Art (synchronem) Vordergrundprozess machen. Dies geschieht jedoch innerhalb einer (…)Liste, die sich vollständig im Hintergrund befindet, sodass die gulpProzesse asynchron ablaufen.
    • gulpSchreiben Sie nach dem Beenden eines der Prozesse seinen Status in eine temporäre Datei und beenden Sie den Prozess "Für immer schlafen".
  • sleep 1 Zum Schutz vor einer Race-Bedingung, bei der der erste Hintergrundprozess abbricht, bevor der zweite die Möglichkeit erhält, seine PID in die Datei zu schreiben.
  • Warten Sie, bis der Vorgang "Für immer schlafen" beendet ist. Dies geschieht , nachdem entweder der gulpProzesse beendet wird , wie oben angegeben.
  • Sehen Sie, welcher Hintergrundprozess beendet wurde. Wenn es fehlgeschlagen ist, töte den anderen.
  • Wenn ein Prozess fehlgeschlagen ist und der andere beendet wurde, warten Sie, bis der zweite abgeschlossen ist, und speichern Sie den Status in einer Datei. Wenn der erste Vorgang erfolgreich abgeschlossen wurde, warten Sie, bis der zweite abgeschlossen ist.
  • Überprüfen Sie den Status der beiden Prozesse.
G-Man sagt, "Monica wiedereinstellen"
quelle
1

Der Vollständigkeit halber habe ich Folgendes verwendet:

#!/bin/bash
(cd frontend && gulp serve) &
(cd backend && gulp serve --verbose) &
wait -n
kill 0

Dies funktioniert für mich unter Git für Windows 2.5.3 64-Bit. Ältere Versionen akzeptieren die -nOption möglicherweise nicht wait.

blah238
quelle
1

Auf meinem System (Centos) waithabe -nich Folgendes getan:

{ sleep 3; echo one;  } &
FOO=$!
{ sleep 6; echo two;  } &
wait $FOO
pkill -P $$

Dies wartet nicht auf "entweder", sondern auf den ersten. Dennoch kann es hilfreich sein, wenn Sie wissen, welcher Server zuerst gestoppt wird.

Ondra Žižka
quelle
Ob waitdie -nOption verfügbar ist oder nicht, hängt von der verwendeten Shell ab, nicht von der Linux-Distribution.
Kusalananda