Bash, wie kann man einige Hintergrundprozesse laufen lassen, aber auf andere warten?

11

Ich habe (noch) eine andere wait, &, &&Kontrollfluss Frage ..

Angenommen, ich habe so etwas wie ein Skript, in dem ich so viel Arbeit wie möglich gleichzeitig erledigen möchte:

# may take some hours
something InputA > IrrelevantA &
something InputB > IrrelevantB &

# may take an hour
(
   somethingElse InputA > OutputA &
   somethingElse InputB > OutputB &
)&& combine OutputA OutputB > Result

...morestuff

Frage 1: combineWarten Sie im Skript, bis beide somethingElseProzesse abgeschlossen sind, während beide somethingProzesse fortgesetzt werden?

Frage 2: Wenn nicht - und ich vermute es nicht - wie kann ich combinenur auf beide somethingElseProzesse warten , während somethingdie oben genannten Prozesse im Hintergrund weiterarbeiten?

Stephen Henderson
quelle

Antworten:

13

In Ihrem Beispiel wird der combineBefehl nur ausgeführt, sobald die Subshell beendet wird (und vorausgesetzt, der letzte Hintergrundprozess wurde ohne Fehler gestartet). Die Subshell wird sofort nach dem Start der Jobs beendet, da kein waitBefehl vorhanden ist.

Wenn Sie einen Befehl ausführen möchten, der auf dem Rückgabewert von zwei oder mehr gleichzeitigen Hintergrundprozessen basiert, kann ich keine andere Möglichkeit sehen, als temporäre Dateien für die Rückgabewerte zu verwenden. Dies liegt daran, dass waitnur der Rückgabewert eines der Prozesse zurückgegeben werden kann, auf die gewartet wird. Da die Hintergrundprozesse in Unterschalen ausgeführt werden müssen, um überhaupt ihre Rückgabewerte zu erhalten, können sie nicht in Variablen gespeichert werden. Du könntest es tun:

something InputA >IrrelevantA &
something InputB >IrrelevantB &

tmp1=$(mktemp)
tmp2=$(mktemp)

( somethingElse InputA >OutputA; echo $? >"$tmp1" ) &
proc1=$!

( somethingElse InputB >OutputB; echo $? >"$tmp2" ) &
proc2=$!

wait "$proc1" "$proc2"

read ret1 <"$tmp1"
read ret2 <"$tmp2"
[ "$ret1" = 0 && "ret2" = 0 ] && combine OutputA OutputB >Result

rm "$tmp1" "$tmp2"

Wenn Sie sich nicht wirklich für die Rückgabewerte interessieren, können Sie die Jobs einfach normal starten und Folgendes verwenden wait:

something InputA >IrrelevantA &
something InputB >IrrelevantB &

somethingElse InputA >OutputA &
proc1=$!

somethingElse InputB >OutputB &
proc2=$!

wait "$proc1" "$proc2"
combine OutputA OutputB >Result
Graeme
quelle
Hallo, ich denke, die 2. Option würde für mich funktionieren ...
Stephen Henderson
3

Würde Prozess Substitution effizienter sein, besonders wenn Sie nicht brauchen , um die Dateien zu speichern OutputAund OutputB, und kümmern sich nur um Result? Wäre dies besonders zeitsparend, da bei langsamen E / A-Vorgängen beim Schreiben auf die Festplatte die Dateien gespeichert werden OutputAund OutputBmöglicherweise die Geschwindigkeit begrenzt wird?

combine  <(somethingElse InputA)  <(somethingElse InputB)  >  Result

Mit der Prozessersetzung können Sie den Befehl einfügen, <(..here..)anstatt die Ausgabe in einer Datei zu speichern und dann im Schritt "Kombinieren" als Eingabe daraus zu lesen.

Wenn der Speicher eine Einschränkung darstellt und die Größe outputAund outputBmehr als das, was der Speicher aufnehmen kann, den gesamten Zweck zunichte macht?

Wird combinewarten , bis beide Prozesse abgeschlossen sind , bevor es zu laufen beginnt?

TW Tan
quelle
Dies ist keine „Gefahr“; Sie tun nicht Ausdruck Ihrer Antwort in Form einer Frage. Im Ernst, Sie haben eine neue Idee, und ich finde sie ziemlich gut. Um auf einige Ihrer Punkte zu antworten: combineWird ausgeführt, sobald die beiden somethingElseBefehle gestartet wurden, aber das ist in Ordnung, da <(…)es sich um Pipes handelt. Es combinewird also einfach gezwungen sein, auf Daten zu warten, wenn diese die somethingElseProzesse überholen . Und weil es sich um Rohre handelt, spielt die Größe keine Rolle. … (Fortsetzung)
G-Man sagt 'Reinstate Monica'
(Fortsetzung)… Das einzige wesentliche Problem, das ich mit Ihrer Antwort habe, ist, dass es nicht möglich ist, den Exit-Status der somethingElseProzesse zu testen - und es ist nicht ganz klar, ob dies für den Fragesteller wichtig ist. Eine Antwort sollte aber auch nicht darin bestehen, solche Fragen zu stellen.
G-Man sagt 'Reinstate Monica'
2

Sie können den folgenden waitBefehl verwenden:

(echo starting & sleep 10 & wait) && echo done

Sie können sehen, dass die "Start" -Zeile sofort passiert und das "Fertig" 10 Sekunden wartet.

psusi
quelle
Im Allgemeinen erfordert das Warten untergeordnete Prozesse derselben Shell. Warten ist dort ziemlich knifflig.
Mikeserv
1
@mikeserv, wovon redest du? Das ist der Punkt: Es wartet auf alle Kinder in dieser Unterschale.
Psusi
Nach meinen ersten Tests funktioniert dies. Ich werde es jetzt auf dem großen Skript versuchen
Stephen Henderson
Genau - Kinder derselben Schale - Unterschalen . Es sollte für jeden Prozess funktionieren, der nicht versucht zu entkommen - oder zu dämonisieren oder was auch immer. Das ist alles, was ich meinte - solange Ihre Prozesse die Prozessleiter respektieren, ist das Warten in Ordnung, aber sobald ein Prozess versucht, sein eigener Prozessleiter zu werden, wird das Warten Probleme haben.
Mikeserv
0

Ich zeige tatsächlich genau, wie so etwas in einer anderen Antwort hier gemacht werden könnte . Diese Antwort bezog sich auf eine Frage, wie sichergestellt werden kann, dass 2 Protokolle von einem Hintergrundprozess verwaltet werden. Deshalb habe ich sie mit 10 demonstriert.

Demo-Skript

cat <<-\DEMO >|${s=/tmp/script} 
printf 'tty is %s\nparent pid is %s\npid is pid=%s\n' \
     "$(tty)" "$PPID" "$$"
exec 1>&2 ; nums=$(seq 0 9)
rm ${files=$(printf "/tmp/file%s\n" $nums)}
for n in $nums ; do { for f in $files ; do
    echo "Line $n" >>"$f" ; done
sleep 1 ; } ; done
#END
DEMO

Führen Sie die Demo aus

s=/tmp/script ;chmod +x $s ;info="$(($s &)2>&- &)"
echo "$info" ; pid="${info##*=}" ; echo
while ps -p $pid >/dev/null ; do sleep 3 ; done
for f in /tmp/file[0-9] ; do
    printf 'path : %s\tline count : %s\n' \
        $f $(<$f wc -l)
done

Ausgabe:

tty is not a tty
parent pid is 1
pid is 12123

path : /tmp/file0    line count : 10
path : /tmp/file1    line count : 10
path : /tmp/file2    line count : 10
path : /tmp/file3    line count : 10
path : /tmp/file4    line count : 10
path : /tmp/file5    line count : 10
path : /tmp/file6    line count : 10
path : /tmp/file7    line count : 10
path : /tmp/file8    line count : 10
path : /tmp/file9    line count : 10

Das Obige zeigt. Es baut und betreibt ein Skript mit dem Namen /tmp/script, chmodist es als ausführbare Datei, und führt es in dem &backgroundvon ein &backgrounded ( subshell ).

Das Skript rms /tmp/file0-910 Dateien und echoeseine Zeile jede Sekunde in alle 10 von ihnen. Ich nehme einige $infoaus dem abgelehnten Prozess auf und präsentiere sie über $(command substitution). While psStandbildberichte zu dem von $pidmir erfassten. Ich weiß, dass sie immer noch ausgeführt werden. sleep.Wenn sie abgeschlossen sind, werden die Zeilen in allen 10 Dateien gezähltwc.

Nachdem Sie einen Prozess auf diese Weise aufgerufen haben, können Sie den ursprünglichen übergeordneten Prozess frei schließen und er wird weiter transportiert - er wird effektiv abgelehnt. Dies bedeutet auch, dass Sie den herkömmlichen waitBefehl nicht verwenden können , aber das Warten auf psdie Rückkehr sollte in jedem Fall robuster sein.

Erwähnenswert ist meiner Meinung nach, dass der Prozess zunächst tatsächlich aufgerufen wird $(command substitution)und printfsich das $infomöchte, damit ich ihn effektiv steuern kann. Aber sobald es seinen Terminalausgang mit exec 1>&2(der in derselben Subshell mit geschlossen ist 2>&-) fallen lässt, entkommt der Prozess und ich muss am anderen Ende darauf warten. Ein bisschen das Beste aus beiden Welten, besonders wenn Sie es für die Handhabung von Eingangsleitungen verwenden, solange Sie sich um alle Umleitungen und Prozessleiter kümmern können.

Alles andere dient hier nur zur Demonstration. Alles was Sie brauchen, um dies auszuführen, ist das Top-Skript und:

info="$(($script_path &)2>&- &)"    

HINWEIS: Dies druckt nur genau das auf das Terminal, was ich demonstrieren wollte. Wie dies festgestellt wurde, wird$PPID,dieser Prozess vom Terminal abgelehnt und ist ein direktes Kind von$PID 1.

Wenn Sie zwei davon gleichzeitig psausführen und auf sie warten möchten, können Sie einfach beide Pids übergeben und warten.

mikeserv
quelle