Mailinglist Archive: opensuse-programming-de (32 mails)
| < Previous | Next > |
Re: [opensuse-programming-de] Bash - Exit Status von Hintergrundjobs ermitteln
- From: Thomas Michalka <Thomas.Michalka@xxxxxx>
- Date: Sat, 30 Jan 2010 16:31:21 +0100
- Message-id: <4B6450C9.1020000@xxxxxx>
Hallo Christian,
Christian Boltz schrieb:
Sollte heißen: exit_status_list="exit_status_list$? "
Stimmt, das sehe ich jetzt auch, denn ich lasse diese zweite
for-Schleife ja erst laufen, wenn ich im Skript an eine Stelle komme, wo
ich alle Rückgabewerte prüfen muss. Ist ein Prozess bis dahin noch nicht
fertig, dann wartet die Schleife halt, bis er fertig ist und ermittelt
die Rückgabewerte der restlichen Hintergrundprozesse danach.
Voraussetzung dafür ist allerdings, dass sich die aufrufende Bash _alle_
Exitcodes ihrer Kindprozesse merkt (was der Fall ist, wie Du mir gezeigt
hast :-D ).
Ich finde es nicht unelegant, zumindest für meinen Bedarf, denn ich muss
gar kein umständliches Polling selber programmieren (das tut die Bash
mit dem wait-Kommando für mich, auch wenn sie wohl nicht wirklich pollt,
sondern vermutlich auf ein Signal der Subshell wartet), auch keine
Ausgaben in temporäre Dateien, u.ä Hilfskonstrukte.
Wenn das mit dem wait bash-intern aber so geht, warum soll man ein
solches Feature nicht nutzen?
Doch, das ist es jetzt dank Deines Hinweises - das war für mich der
Schlüssel :-)
So ähnlich habe ich es auch probiert, aber weil ich dazwischen ein
einfaches wait (ohne Parameter) abgesetzt habe, ging das nicht. Ich habe
auch noch getestet, ob man zwischen dem Start des Hintergrundprozesses
und dem wait $PID beliebige andere Befehle abarbeiten kann. Dies
scheint, bis auf das einfache wait eben, der Fall zu sein.
Das wait-Kommando scheint die Liste der Kindprozesse der aufrufenden
Bash zu löschen, weswegen ein späteres wait $PID folgende Meldung ergibt:
bash: wait: pid 30738 is not a child of this shell
Das wär's natürlich ...
Durch Lektüre des Manpage-Abschnitts konnte ich allerdings nicht
herausfinden, ob ein trap <command> <signal> auch einen Rückgabewert
zurückerhält und an <command> weitergibt.
Hätte das sonst einen Sinn?
Das wäre dann zu wenig, weil ich danach ggf. nicht sagen könnte, welcher
Kindprozess gescheitert ist.
Das Überprüfen von Exitcodes von Hintergrundprozessen halte ich schon
für elementar, weil man sonst bei asynchronen Aufrufen keine vernünftige
Fehlerbehandlung durchführen könnte.
Wenn ich das richtig sehe, willst Du hier eine Subshell starten. Mit den
beiden Kommandos rsync und echo darin.
Eigentlich müsste dann die PID der Subshell in der PID_LIST landen,
nicht die von echo und erst recht nicht die von rsync, denn das ist
nicht das letzte Kommando in der Subshell.
Das ginge eleganter ohne temporäre Dateien: man kann die PIDs in einer
Liste speichern und später wieder in einer (Endlos-)Schleife reihum
lesen und mit
if ! ps r | grep -q "$PID" ; then ... ; fi
Wird grep fündig, dann existiert der Prozess noch und läuft auch (r =
running), wenn nicht, dann ist der Prozess wohl fertig (er könnte aber
auch unterbrochen sein oder einen anderen Zustand einnehmen).
Das geht aber doch auch innerhalb meines Skripts, ganz ohne temporäre
Dateien, denn durch die erste for-Schleife gibt es eine eindeutige
Zuordnung zwischen PID_LIST und der Liste der zu sichernden
Verzeichnisse B_SRCDIR_LIST.
Kann ich auch mit PID_LIST und B_SRCDIR_LIST, wie meine zweite
for-Schleife zeigt (zugegebenermaßen nur unvollständig und mit einem
Fehler, aber prinzipiell).
Diese Lösung wäre in Betracht gekommen, wenn sich die Bash die Exitcodes
ihrer Kindprozesse nicht merken würde. Aber dass sie es tut, ist doch
elegant!
# in der aufrufenden Shell gespeichert werden?
# Funktioniert aber wohl nicht, da der Exit-Status
# des letzten ausgeführten Kommandos, von 'do'
# also, zugewiesen würde
done # (durfte ich doch ergänzen?)
Das Problem hier ist aber doch genau dasselbe, nämlich dass Du nicht
weißt, wann child_finished in der Subshell aufgerufen wird. Daher weißt
Du auch nicht, wann und wie Du den Return-Wert von welchem
Hintergrundprozess in die aufrufende Shell in $? zurückgeliefert
bekommst. Vielleicht ginge es, in der Callback-Funktion den Rückgabewert
in eine globale Variable zu schreiben, aber mir ist momentan nicht ganz
klar, ob das mit dem neuen Environment der Subshell hinhaut.
Außerdem sehe ich den Sinn des Durchreichens des Returnwertes nicht ein,
da eine Subshell als Exitcode doch ohnehin den Exit-Status des in ihr
zuletzt ausgeführten Kommandos zurückliefert.
Kurz gesagt:
Ich verstehe nicht, wie Du hier das Problem löst, dass die aufrufende
Shell die Exit-Stati erhält und nach der Beendigung des letzten aktiven
Hintergrundprozesses alle irgendwo gespeichert hat.
Und wie signalisiert child_finished () der aufrufenden Shell die
Beendigung der Subshell und damit von rsync?
Wenn Du wait (natürlich in der Form wait $PID) verwendest, wie Du
kommentierst, dann kannst Du Dir child_finished auch sparen, denn
for SRC_DIR in $SRC_DIR_LIST ; do
rsync <options> $SRC_DIR $DEST_DIR &
PID_LIST="$PID_LIST$! "
done
liefert Dir alle PIDs, die Du dann in einer später aufgerufenen zweiten
for-Schleife eindeutig den Return-Werten mittels wait $PID zuordnen
kannst (von mir aus auch in einem Array, damit man bei der Kodierung der
Zuordnung Fehler vermeidet).
Gruß, Tom
--
To unsubscribe, e-mail: opensuse-programming-de+unsubscribe@xxxxxxxxxxxx
For additional commands, e-mail: opensuse-programming-de+help@xxxxxxxxxxxx
Christian Boltz schrieb:
Hallo Thomas, hallo Leute,
Am Donnerstag, 28. Januar 2010 schrieb Thomas Michalka:
# Alle Backups parallel laufen lassen
for SRC_DIR in $SRC_DIR_LIST ; do
rsync <options> $SRC_DIR $DEST_DIR &
PID_LIST="$! "
done
# Jetzt aber auf den Abschluss aller Backups warten
wait $PID_LIST
exit_status=$?
# Der Rest des Skripts kommt hier ...
Was aber passiert, wenn man in einer weiteren Schleife die
Rückgabewerte aller rsync-Prozesse ermittelt, etwa so:
for PID in $PID_LIST ; do
wait $PID
exit_status_list="$? "
Sollte heißen: exit_status_list="exit_status_list$? "
done
Hier zweifle ich stark, ob das überhaupt funktioniert, denn
1.
Hier wird der Sinn der oben gewollten Asynchronität der
Hintergrundjobs konterkariert, weil für eine PID, deren Prozess noch
nicht terminiert ist, in dieser for-Schleife gewartet wird, bis er
beendet ist.
Wäre in Deinem Fall ja nicht wirklich ein Problem, wenn ich die
Kommentare in Deinem Script ("Jetzt aber auf den Abschluss aller Backups
warten") richtig interpretiere.
Stimmt, das sehe ich jetzt auch, denn ich lasse diese zweite
for-Schleife ja erst laufen, wenn ich im Skript an eine Stelle komme, wo
ich alle Rückgabewerte prüfen muss. Ist ein Prozess bis dahin noch nicht
fertig, dann wartet die Schleife halt, bis er fertig ist und ermittelt
die Rückgabewerte der restlichen Hintergrundprozesse danach.
Voraussetzung dafür ist allerdings, dass sich die aufrufende Bash _alle_
Exitcodes ihrer Kindprozesse merkt (was der Fall ist, wie Du mir gezeigt
hast :-D ).
Unschön ist es trotzdem.
Ich finde es nicht unelegant, zumindest für meinen Bedarf, denn ich muss
gar kein umständliches Polling selber programmieren (das tut die Bash
mit dem wait-Kommando für mich, auch wenn sie wohl nicht wirklich pollt,
sondern vermutlich auf ein Signal der Subshell wartet), auch keine
Ausgaben in temporäre Dateien, u.ä Hilfskonstrukte.
Wenn das mit dem wait bash-intern aber so geht, warum soll man ein
solches Feature nicht nutzen?
2.
Es ist nicht klar, ob wait $PID bei einem vor der for-Schleife schon
beendeten Job noch den exit_status zurückgibt und was demnach in der
exit_status_list landet.
Doch, das ist es jetzt dank Deines Hinweises - das war für mich der
Schlüssel :-)
Teste das doch einfach selbst ;-)
# true &
[1] 16758
[1]+ Fertig true
# false &
[1] 16760
[1]+ Exit 1 false
# wait 16758
# echo $?
0
# wait 16760
# echo $?
1
-> ja, die Bash merkt sich den Exitcode ihrer Kindprozesse.
So ähnlich habe ich es auch probiert, aber weil ich dazwischen ein
einfaches wait (ohne Parameter) abgesetzt habe, ging das nicht. Ich habe
auch noch getestet, ob man zwischen dem Start des Hintergrundprozesses
und dem wait $PID beliebige andere Befehle abarbeiten kann. Dies
scheint, bis auf das einfache wait eben, der Fall zu sein.
Das wait-Kommando scheint die Liste der Kindprozesse der aufrufenden
Bash zu löschen, weswegen ein späteres wait $PID folgende Meldung ergibt:
bash: wait: pid 30738 is not a child of this shell
Ich hab noch was interessantes in der Bash-manpage gefunden:
Any trap on SIGCHLD is executed for each child that exits.
Die Hintergrundprozesse können sich also per Callback melden, wenn sie
fertig sind ;-)
Das wär's natürlich ...
Durch Lektüre des Manpage-Abschnitts konnte ich allerdings nicht
herausfinden, ob ein trap <command> <signal> auch einen Rückgabewert
zurückerhält und an <command> weitergibt.
Ich weiß nicht, ob da die PID irgendwie mitgeliefert wird, aber immerhin
Hätte das sonst einen Sinn?
könntest Du damit einen Zähler a la "noch 3 Kindprozesse" bauen.
Das wäre dann zu wenig, weil ich danach ggf. nicht sagen könnte, welcher
Kindprozess gescheitert ist.
(Weiter unten hab ich Dir auch noch ein besseres Callback notiert.)
Willkommen wäre mir ein Hinweis, wo man solche grundlegenden
Bash-Programmier-Fragen und -Lösungen nachschlagen kann.
Nunja, sowas wie Deine Frage würde ich nicht als "grundlegende Bash-
Programmier-Frage" einstufen ;-)
Das Überprüfen von Exitcodes von Hintergrundprozessen halte ich schon
für elementar, weil man sonst bei asynchronen Aufrufen keine vernünftige
Fehlerbehandlung durchführen könnte.
Ich habe dazu auch noch die interessante Aufgabenstellung "Variablen aus
der Subshell"[1] in Erinnerung. Vielleicht kannst Du ja mit einem
zusätzlichen Ausgabekanal, auf dem die Hintergrundprozesse schreiben,
was anfangen (keine Ahnung, wie sich meine Tricks mit
Hintergrundprozessen vertragen - ich habe das nie getestet).
So, und jetzt die pragmatische Lösung: verwende eine andere Methode zur
Erkennung, welche Prozesse noch laufen ;-)
Eine Variante wäre beispielsweise, pro Prozess ein Tempfile[2] mit dem
Exitcode anzulegen:
for SRC_DIR in $SRC_DIR_LIST ; do
{
rsync <options> $SRC_DIR $DEST_DIR ;
echo $? > tmp_$SRC_DIR ; # [2]
}&
PID_LIST="$! "
Wenn ich das richtig sehe, willst Du hier eine Subshell starten. Mit den
beiden Kommandos rsync und echo darin.
Eigentlich müsste dann die PID der Subshell in der PID_LIST landen,
nicht die von echo und erst recht nicht die von rsync, denn das ist
nicht das letzte Kommando in der Subshell.
Damit schlägst Du ein paar Fliegen mit einer Klappe:
- Du siehst, wann ein Prozess fertig ist:
test -f tmp_irgendwas && echo "fertig"
Das ginge eleganter ohne temporäre Dateien: man kann die PIDs in einer
Liste speichern und später wieder in einer (Endlos-)Schleife reihum
lesen und mit
if ! ps r | grep -q "$PID" ; then ... ; fi
Wird grep fündig, dann existiert der Prozess noch und läuft auch (r =
running), wenn nicht, dann ist der Prozess wohl fertig (er könnte aber
auch unterbrochen sein oder einen anderen Zustand einnehmen).
- Im Tempfile steht der Exitcode, den Du problemlos einem Prozess
(wichtiger: einem Backup-Verzeichnis) zuordnen kannst
Das geht aber doch auch innerhalb meines Skripts, ganz ohne temporäre
Dateien, denn durch die erste for-Schleife gibt es eine eindeutige
Zuordnung zwischen PID_LIST und der Liste der zu sichernden
Verzeichnisse B_SRCDIR_LIST.
- Du kannst problemlos über alle Prozesse/Tempfiles einen Loop machen,
ohne irgendwie geblockt zu werden ("test -f" oder "test -s")
Kann ich auch mit PID_LIST und B_SRCDIR_LIST, wie meine zweite
for-Schleife zeigt (zugegebenermaßen nur unvollständig und mit einem
Fehler, aber prinzipiell).
- Wenn alle angefragten Tempfiles existieren und jeweils eine "0"
enthalten, ist das Backup komplett und erfolgreich ;-)
Diese Lösung wäre in Betracht gekommen, wenn sich die Bash die Exitcodes
ihrer Kindprozesse nicht merken würde. Aber dass sie es tut, ist doch
elegant!
Noch eine Variante ohne Tempfiles: Bau Dir selbst ein Callback ;-)exitstatus=$? # Sollte nicht so oder ähnlich der Exit-Status
Das ist besser als SIGCHLD, weil Du damit den Exitstatus und z. B. das
gesicherte Verzeichnis durchreichen kannst. Bei der Gelegenheit kannst
Du auch gleich ein Logfile schreiben usw. - die Lösung ist flexibel ;-)
function child_finished() {
echo "Status: $1"
echo "Dir: $2"
return $1 # exit-Status durchreichen, damit er z. B. für wait
# sichtbar wird
}
for SRC_DIR in $SRC_DIR_LIST ; do
{
rsync <options> $SRC_DIR $DEST_DIR ;
child_finished $? $SRC_DIR
} &
# in der aufrufenden Shell gespeichert werden?
# Funktioniert aber wohl nicht, da der Exit-Status
# des letzten ausgeführten Kommandos, von 'do'
# also, zugewiesen würde
done # (durfte ich doch ergänzen?)
Das Problem hier ist aber doch genau dasselbe, nämlich dass Du nicht
weißt, wann child_finished in der Subshell aufgerufen wird. Daher weißt
Du auch nicht, wann und wie Du den Return-Wert von welchem
Hintergrundprozess in die aufrufende Shell in $? zurückgeliefert
bekommst. Vielleicht ginge es, in der Callback-Funktion den Rückgabewert
in eine globale Variable zu schreiben, aber mir ist momentan nicht ganz
klar, ob das mit dem neuen Environment der Subshell hinhaut.
Außerdem sehe ich den Sinn des Durchreichens des Returnwertes nicht ein,
da eine Subshell als Exitcode doch ohnehin den Exit-Status des in ihr
zuletzt ausgeführten Kommandos zurückliefert.
Kurz gesagt:
Ich verstehe nicht, wie Du hier das Problem löst, dass die aufrufende
Shell die Exit-Stati erhält und nach der Beendigung des letzten aktiven
Hintergrundprozesses alle irgendwo gespeichert hat.
Und wie signalisiert child_finished () der aufrufenden Shell die
Beendigung der Subshell und damit von rsync?
Wenn Du wait (natürlich in der Form wait $PID) verwendest, wie Du
kommentierst, dann kannst Du Dir child_finished auch sparen, denn
for SRC_DIR in $SRC_DIR_LIST ; do
rsync <options> $SRC_DIR $DEST_DIR &
PID_LIST="$PID_LIST$! "
done
liefert Dir alle PIDs, die Du dann in einer später aufgerufenen zweiten
for-Schleife eindeutig den Return-Werten mittels wait $PID zuordnen
kannst (von mir aus auch in einem Array, damit man bei der Kodierung der
Zuordnung Fehler vermeidet).
Gruß, Tom
--
To unsubscribe, e-mail: opensuse-programming-de+unsubscribe@xxxxxxxxxxxx
For additional commands, e-mail: opensuse-programming-de+help@xxxxxxxxxxxx
| < Previous | Next > |