Re: Bash - Exit Status von Hintergrundjobs ermitteln

Christian Boltz <suse-/[email protected]> Fri, 29 Jan 2010 02:20:09 +0100
Newsgroups gmane.linux.suse.programming
Message-ID <[email protected]>
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="$? "
> 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. Unschön ist es trotzdem.

>  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.

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.


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 ;-) 
Ich weiß nicht, ob da die PID irgendwie mitgeliefert wird, aber immerhin 
könntest Du damit einen Zähler a la "noch 3 Kindprozesse" bauen.
(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 ;-)

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="$! "

Damit schlägst Du ein paar Fliegen mit einer Klappe:
- Du siehst, wann ein Prozess fertig ist:
      test -f tmp_irgendwas && echo "fertig"
- Im Tempfile steht der Exitcode, den Du problemlos einem Prozess
  (wichtiger: einem Backup-Verzeichnis) zuordnen kannst
- Du kannst problemlos über alle Prozesse/Tempfiles einen Loop machen,
  ohne irgendwie geblockt zu werden ("test -f" oder "test -s")
- Wenn alle angefragten Tempfiles existieren und jeweils eine "0" 
  enthalten, ist das Backup komplett und erfolgreich ;-)


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
    } &

So, das sollten genug Anregungen für heute Nacht sein. Wie Du siehst, 
hat Programmierung auch immer ein Stück weit mit Kreativität zu tun ;-)


Gruß

Christian Boltz

[1] www.cboltz.de/de/linux/bash/

[2] dass das Ganze innerhalb eines "mktemp -d"-Verzeichnisses ablaufen
    sollte, versteht sich von selbst, oder?
-- 
> Kann mir bitte jemand das Script für die Abmeldung von dieser Liste
> schicken.
ist schon wieder der erste April??
[> Josef Schwendt und Andreas Kyek in suse-linux]