Wie kann man in einem Bash-Skript auf mehrere Unterprozesse warten, die aus diesem Skript hervorgegangen sind, um den Exit-Code zu beenden und zurückzugeben! = 0, wenn einer der Unterprozesse mit Code endet! = 0?
Einfaches Skript:
#!/bin/bash
for i in `seq 0 9`; do
doCalculations $i &
done
wait
Das obige Skript wartet auf alle 10 erzeugten Unterprozesse, gibt jedoch immer den Exit-Status 0 (siehe help wait
). Wie kann ich dieses Skript so ändern, dass es den Beendigungsstatus von erzeugten Unterprozessen erkennt und den Beendigungscode 1 zurückgibt, wenn einer der Unterprozesse mit Code endet! = 0?
Gibt es dafür eine bessere Lösung, als PIDs der Unterprozesse zu sammeln, in der richtigen Reihenfolge auf sie zu warten und den Exit-Status zu summieren?
wait -n
ist in der modernen Bash verfügbar und kann nur zurückgegeben werden, wenn der erste / nächste Befehl abgeschlossen ist.wait -n
hat ein kleines Problem: Wenn keine untergeordneten Jobs mehr vorhanden sind (auch als Race-Bedingung bezeichnet), wird ein Exit-Status ungleich Null (fehlgeschlagen) zurückgegeben, der von einem fehlgeschlagenen untergeordneten Prozess nicht zu unterscheiden ist.Antworten:
wait
Außerdem (optional) wird die PID des Prozesses zum Warten verwendet, und zwar mit $! Sie erhalten die PID des zuletzt im Hintergrund gestarteten Befehls. Ändern Sie die Schleife, um die PID jedes erzeugten Unterprozesses in einem Array zu speichern, und warten Sie dann erneut auf jede PID.quelle
for i in $n_procs; do ./procs[${i}] & ; pids[${i}]=$!; done; for pid in ${pids[*]}; do wait $pid; done;
richtig?http://jeremy.zawodny.com/blog/archives/010717.html :
quelle
jobs -p
gibt PIDs von Unterprozessen an, die sich im Ausführungsstatus befinden. Ein Prozess wird übersprungen, wenn der zuvor abgeschlossene Prozessjobs -p
aufgerufen wird. Wenn also einer der Unterprozesse zuvor endetjobs -p
, geht der Exit-Status dieses Prozesses verloren.jobs -p
gibt keine PIDs von Unterprozessen an, sondern GPIDs . Die Wartelogik scheint sowieso zu funktionieren, sie wartet immer auf die Gruppe, wenn eine solche Gruppe existiert, und pid, wenn nicht, aber es ist gut, sich dessen bewusst zu sein. Insbesondere, wenn man darauf aufbaut und so etwas wie das Senden von Nachrichten an den Unterprozess einbezieht, in dem Fall die Syntax ist unterschiedlich, je nachdem, ob Sie PIDs oder GPIDs haben .. dhkill -- -$GPID
vskill $PID
Hier ist ein einfaches Beispiel mit
wait
.Führen Sie einige Prozesse aus:
Dann warten Sie mit
wait
Befehl auf sie:Oder einfach
wait
(ohne Argumente) für alle.Dies wartet, bis alle Jobs im Hintergrund abgeschlossen sind.
Wenn die
-n
Option angegeben ist, wartet sie auf das Beenden des nächsten Jobs und gibt seinen Beendigungsstatus zurück.Siehe:
help wait
undhelp jobs
für die Syntax.Der Nachteil ist jedoch, dass nur der Status der letzten ID zurückgegeben wird. Sie müssen daher den Status für jeden Unterprozess überprüfen und in der Variablen speichern.
Oder lassen Sie Ihre Berechnungsfunktion eine Datei bei einem Fehler erstellen (leer oder mit Fehlerprotokoll) und überprüfen Sie diese Datei, falls vorhanden, z
quelle
sleep 20 && true
undsleep 20 && false
- dh: Ersetzen Sie diese durch Ihre Funktion (en). Um zu verstehen&&
und||
, führen Sieman bash
'/' (Suche) aus und geben Sie '^ * Lists' (ein&&
||
||
STDERR ebenfalls fehlschlägt.wait
Wenn Sie GNU Parallel installiert haben, können Sie Folgendes tun:
GNU Parallel gibt Ihnen den Exit-Code:
0 - Alle Jobs wurden ohne Fehler ausgeführt.
1-253 - Einige der Jobs sind fehlgeschlagen. Der Exit-Status gibt die Anzahl der fehlgeschlagenen Jobs an
254 - Mehr als 253 Jobs sind fehlgeschlagen.
255 - Anderer Fehler.
Sehen Sie sich die Intro-Videos an, um mehr zu erfahren: http://pi.dk/1
quelle
doCalculations
funktioniert, wie es in demselben Skript definiert ist (obwohl das OP über diese Anforderung nicht klar war). Wenn ich es versuche,parallel
sagt/bin/bash: doCalculations: command not found
(sagt es 10 mal für dasseq 0 9
obige Beispiel). Sehen Sie hier für dieses Problem zu umgehen.xargs
hat einige Möglichkeiten, Jobs über die-P
Option parallel zu starten . Von hier aus :export -f doCalculations ; seq 0 9 |xargs -P 0 -n 1 -I{} bash -c "doCalculations {}"
. Einschränkungen vonxargs
sind in der Manpage für aufgeführtparallel
.doCalculations
sich auf andere skriptinterne Umgebungsvariablen (benutzerdefiniertPATH
usw.) stützen , müssen diese wahrscheinlichexport
vor dem Start explizit bearbeitet werdenparallel
.wget -O - pi.dk/3 | sh
Sie keine Verwirrungen bekommen. Wenn Ihr Packager die Dinge für Sie durcheinander gebracht hat, empfehle ich Ihnen, das Problem mit Ihrem Packager anzusprechen. Variablen und Funktionen sollten exportiert werden (export -f), damit GNU Parallel sie sehen kann (sieheman parallel
: gnu.org/software/parallel/… )Wie wäre es einfach:
Aktualisieren:
Wie von mehreren Kommentatoren erwähnt, wartet das Obige darauf, dass alle Prozesse abgeschlossen sind, bevor es fortgesetzt wird. Es wird jedoch nicht beendet und schlägt fehl, wenn einer von ihnen fehlschlägt. Dies kann mit der folgenden von @Bryan, @SamBrightman und anderen vorgeschlagenen Änderung geschehen ::
quelle
for pid in $pids; do wait $pid; done
help wait
- mit mehreren IDs wird nurwait
der Exit-Code des letzten zurückgegeben, wie @ vlad-frolov oben sagte.wait
aufgerufen wird? Es stellt sich heraus, dass dies kein Problem ist: Wenn Siewait
einen Prozess beenden, der bereits beendet wurde,wait
wird er sofort mit dem Status des bereits beendeten Prozesses beendet. (Danke,bash
Autoren!)Folgendes habe ich mir bisher ausgedacht. Ich würde gerne sehen, wie der Schlafbefehl unterbrochen werden kann, wenn ein Kind beendet wird, damit man sich nicht
WAITALL_DELAY
auf seine Verwendung einstellen muss.quelle
Um dies zu parallelisieren ...
Übersetzen Sie es in diese ...
--max-procs
, wie viel Parallelität Sie möchten (0
bedeutet "alles auf einmal").xargs
- es wird jedoch nicht immer standardmäßig installiert.for
Schleife ist in diesem Beispiel nicht unbedingt erforderlich, daecho $i
im Grunde nur die Ausgabe von$(whatever_list
) neu generiert wird . Ich denke nur, dass die Verwendung desfor
Schlüsselworts es ein wenig einfacher macht, zu sehen, was los ist.Hier ist ein vereinfachtes Arbeitsbeispiel ...
quelle
--max-procs
: Wie erhalte ich die Anzahl der CPUs / Kerne unter Linux über die Befehlszeile?Ich glaube nicht, dass es mit Bashs eingebauter Funktionalität möglich ist.
Sie können eine Benachrichtigung erhalten, wenn ein Kind beendet wird:
Es gibt jedoch keine offensichtliche Möglichkeit, den Ausgangsstatus des Kindes im Signalhandler abzurufen.
Das Abrufen dieses
wait
untergeordneten Status ist normalerweise Aufgabe der Funktionsfamilie in den POSIX-APIs der unteren Ebene. Leider ist die Unterstützung von Bash dafür begrenzt - Sie können auf einen bestimmten untergeordneten Prozess warten (und dessen Beendigungsstatus erhalten) oder Sie können auf alle warten und erhalten immer ein 0-Ergebnis.Was unmöglich erscheint, ist das Äquivalent von
waitpid(-1)
, das blockiert, bis ein untergeordneter Prozess zurückkehrt.quelle
Ich sehe viele gute Beispiele hier aufgelistet, wollte auch meine einwerfen.
Ich verwende etwas sehr Ähnliches, um Server / Dienste parallel zu starten / stoppen und jeden Exit-Status zu überprüfen. Funktioniert gut für mich. Hoffe das hilft jemandem!
quelle
trap "kill 0" EXIT
Dies ist etwas, das ich benutze:
quelle
Der folgende Code wartet auf den Abschluss aller Berechnungen und gibt den Exit-Status 1 zurück, wenn eine der doCalculations fehlschlägt.
quelle
Speichern Sie die Ergebnisse einfach aus der Shell, z. B. in einer Datei.
quelle
Hier ist meine Version, die für mehrere Pids funktioniert, Warnungen protokolliert, wenn die Ausführung zu lange dauert, und die Unterprozesse stoppt, wenn die Ausführung länger als ein bestimmter Wert dauert.
Warten Sie beispielsweise, bis alle drei Prozesse abgeschlossen sind, protokollieren Sie eine Warnung, wenn die Ausführung länger als 5 Sekunden dauert, und stoppen Sie alle Prozesse, wenn die Ausführung länger als 120 Sekunden dauert. Beenden Sie das Programm nicht bei Fehlern.
quelle
Wenn Sie Bash 4.2 oder höher zur Verfügung haben, kann Folgendes für Sie hilfreich sein. Es verwendet assoziative Arrays, um Aufgabennamen und ihren "Code" sowie Aufgabennamen und ihre Pids zu speichern. Ich habe auch eine einfache Methode zur Ratenbegrenzung eingebaut, die nützlich sein kann, wenn Ihre Aufgaben viel CPU- oder E / A-Zeit beanspruchen und Sie die Anzahl gleichzeitiger Aufgaben begrenzen möchten.
Das Skript startet alle Aufgaben in der ersten Schleife und verwendet die Ergebnisse in der zweiten.
Dies ist ein bisschen übertrieben für einfache Fälle, aber es erlaubt ziemlich ordentliche Sachen. Beispielsweise kann man Fehlermeldungen für jede Aufgabe in einem anderen assoziativen Array speichern und drucken, nachdem sich alles beruhigt hat.
quelle
Ich habe gerade ein Skript an den Hintergrund angepasst und einen Prozess parallelisiert.
Ich habe einige Experimente durchgeführt (unter Solaris mit bash und ksh) und festgestellt, dass 'wait' den Exit-Status ausgibt, wenn er nicht Null ist, oder eine Liste von Jobs, die einen Exit ungleich Null zurückgeben, wenn kein PID-Argument angegeben wird. Z.B
Bash:
Ksh:
Diese Ausgabe wird in stderr geschrieben, daher könnte eine einfache Lösung für das OP-Beispiel sein:
Während dies:
gibt auch eine Zählung zurück, jedoch ohne die tmp-Datei. Dies kann auch folgendermaßen verwendet werden, zum Beispiel:
Dies ist jedoch nicht viel nützlicher als die tmp-Datei IMO. Ich konnte keinen nützlichen Weg finden, um die tmp-Datei zu vermeiden und gleichzeitig zu vermeiden, dass das "Warten" in einer Subshell ausgeführt wird, was überhaupt nicht funktioniert.
quelle
Ich habe es versucht und die besten Teile aus den anderen Beispielen hier kombiniert. Dieses Skript führt die
checkpids
Funktion aus, wenn ein Hintergrundprozess beendet wird, und gibt den Beendigungsstatus aus, ohne auf Abfragen zurückzugreifen.quelle
set -m
ermöglicht es Ihnen, fg & bg in einem Skript zu verwendenfg
hat nicht nur den letzten Prozess in den Vordergrund gestellt, sondern auch den gleichen Beendigungsstatus wie der Prozess, den er in den Vordergrund stelltwhile fg
stoppt die Schleife, wennfg
Exits mit einem Exit-Status ungleich Null beendet werdenLeider wird dies nicht behandelt, wenn ein Prozess im Hintergrund mit einem Exit-Status ungleich Null beendet wird. (Die Schleife wird nicht sofort beendet. Sie wartet, bis die vorherigen Prozesse abgeschlossen sind.)
quelle
Hier gibt es bereits viele Antworten, aber ich bin überrascht, dass niemand die Verwendung von Arrays vorgeschlagen hat ... Also hier ist, was ich getan habe - dies könnte für einige in Zukunft nützlich sein.
quelle
Dies funktioniert, sollte genauso gut sein, wenn nicht besser als die Antwort von @ HoverHell!
und natürlich habe ich dieses Skript in einem NPM-Projekt verewigt, mit dem Sie Bash-Befehle parallel ausführen können, was zum Testen nützlich ist:
https://github.com/ORESoftware/generic-subshell
quelle
trap $? $$
scheint den Exit-Code auf 0 und die PID auf die aktuell laufende Bash-Shell zu setzen, jedes Mal für michFalle ist dein Freund. Sie können ERR in vielen Systemen abfangen. Sie können EXIT oder DEBUG abfangen, um nach jedem Befehl einen Code auszuführen.
Dies zusätzlich zu allen Standardsignalen.
quelle
Die
set -e
oben macht Ihren Skript Stopp eines Fehler auftritt .expect
wird zurückgegeben,1
wenn ein Subjob fehlgeschlagen ist.quelle
Genau zu diesem Zweck habe ich eine
bash
Funktion namens geschrieben:for
.Hinweis :
:for
Der Exit-Code der fehlerhaften Funktion wird nicht nur beibehalten und zurückgegeben, sondern auch alle parallel ausgeführten Instanzen werden beendet. Was in diesem Fall möglicherweise nicht benötigt wird.Verwendungszweck
for.sh
::Verweise
quelle
Ich habe das kürzlich benutzt (danke an Alnitak):
Von dort aus kann man leicht extrapolieren und einen Auslöser haben (eine Datei berühren, ein Signal senden) und die Zählkriterien ändern (berührte Zähldateien oder was auch immer), um auf diesen Auslöser zu reagieren. Oder wenn Sie nur 'irgendeinen' RC ungleich Null wollen, beenden Sie einfach die Sperre von save_status.
quelle
Ich brauchte das, aber der Zielprozess war kein Kind der aktuellen Shell. In diesem Fall
wait $PID
funktioniert es nicht. Ich habe stattdessen die folgende Alternative gefunden:Dies hängt vom Vorhandensein von Prozessen ab , die möglicherweise nicht verfügbar sind (Mac bietet sie beispielsweise nicht an). Aus Gründen der Portabilität können Sie stattdessen Folgendes verwenden:
quelle
Das Einfangen des CHLD-Signals funktioniert möglicherweise nicht, da Sie einige Signale verlieren können, wenn sie gleichzeitig ankommen.
quelle
Die Lösung zum Warten auf mehrere Unterprozesse und zum Beenden, wenn einer von ihnen mit einem Statuscode ungleich Null beendet wird, besteht in der Verwendung von 'wait -n'.
Der Statuscode '127' steht für einen nicht vorhandenen Prozess, was bedeutet, dass das Kind möglicherweise beendet wurde.
quelle
Warten Sie auf alle Jobs und geben Sie den Exit-Code des letzten fehlgeschlagenen Jobs zurück. Im Gegensatz zu den oben genannten Lösungen erfordert dies keine PID-Einsparung. Einfach weg und warten.
quelle
Es kann vorkommen, dass der Prozess abgeschlossen ist, bevor auf den Prozess gewartet wird. Wenn wir das Warten auf einen bereits abgeschlossenen Prozess auslösen, wird ein Fehler ausgelöst, da pid kein untergeordnetes Element dieser Shell ist. Um solche Fälle zu vermeiden, kann die folgende Funktion verwendet werden, um festzustellen, ob der Prozess abgeschlossen ist oder nicht:
quelle
Ich denke, dass die einfachste Möglichkeit, Jobs parallel auszuführen und den Status zu überprüfen, die Verwendung temporärer Dateien ist. Es gibt bereits einige ähnliche Antworten (zB Nietzche-jou und mug896).
Der obige Code ist nicht threadsicher. Wenn Sie befürchten, dass der obige Code gleichzeitig mit sich selbst ausgeführt wird, ist es besser, einen eindeutigeren Dateinamen zu verwenden, z. B. fail. $$. Die letzte Zeile soll die Anforderung erfüllen: "Exit-Code 1 zurückgeben, wenn einer der Unterprozesse mit Code endet! = 0?" Ich warf eine zusätzliche Anforderung hinein, um aufzuräumen. Es mag klarer gewesen sein, es so zu schreiben:
Hier ist ein ähnliches Snippet zum Sammeln von Ergebnissen aus mehreren Jobs: Ich erstelle ein temporäres Verzeichnis, schreibe die Ausgaben aller Unteraufgaben in eine separate Datei und speichere sie dann zur Überprüfung. Dies passt nicht wirklich zu der Frage - ich werfe sie als Bonus ein:
quelle
Ich bin fast in die Falle
jobs -p
geraten, PIDs zu sammeln, was nicht funktioniert, wenn das Kind bereits beendet wurde, wie im folgenden Skript gezeigt. Die Lösung, die ich gewählt habe, war einfachwait -n
N-mal anzurufen , wobei N die Anzahl meiner Kinder ist, die ich zufällig deterministisch kenne.Ausgabe:
quelle