Hallo Christian, 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 ;-) 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 } & exitstatus=$? # Sollte nicht so oder ähnlich der Exit-Status # 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@opensuse.org For additional commands, e-mail: opensuse-programming-de+help@opensuse.org