765 Stimmen

Wie kann man in der Bash warten, bis mehrere Unterprozesse beendet sind, und den Exit-Code !=0 zurückgeben, wenn ein Unterprozess mit dem Code !=0 endet?

Wie kann man in einem Bash-Skript warten, bis mehrere von diesem Skript erzeugte Unterprozesse beendet sind, und dann einen Exit-Code zurückgeben !=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 gespawnten Unterprozesse, aber es gibt immer den Exit-Status 0 (siehe help wait ). Wie kann ich dieses Skript so ändern, dass es den Exit-Status der gespawnten Unterprozesse erkennt und den Exit-Code 1 wenn einer der Unterprozesse mit Code endet !=0 ?

Gibt es dafür eine bessere Lösung als das Sammeln der PIDs der Unterprozesse, das Warten auf sie in der Reihenfolge und die Summe der Exit-Status?

2 Stimmen

Dies könnte erheblich verbessert werden, um auf folgende Punkte einzugehen wait -n die in der modernen Bash verfügbar ist, um nur zurückzukehren, wenn der erste/nächste Befehl abgeschlossen ist.

0 Stimmen

Wenn Sie mit der Bash testen wollen, versuchen Sie dies: github.com/sstephenson/bats

3 Stimmen

Die aktive Entwicklung von BATS hat sich auf github.com/bats-core/bats-core

6voto

Jayen Punkte 5149
#!/bin/bash
set -m
for i in `seq 0 9`; do
  doCalculations $i &
done
while fg; do true; done
  • set -m ermöglicht die Verwendung von fg & bg in einem Skript
  • fg nicht nur den letzten Prozess in den Vordergrund stellt, sondern auch denselben Exit-Status hat wie der Prozess, den er in den Vordergrund stellt
  • while fg stoppt die Schleife, wenn eine fg beendet sich mit einem Exit-Status ungleich Null

Leider wird damit nicht der Fall behandelt, dass ein Prozess im Hintergrund mit einem Exit-Status ungleich Null beendet wird. (Die Schleife wird nicht sofort beendet, sondern es wird gewartet, bis die vorherigen Prozesse abgeschlossen sind.)

6voto

stefanct Punkte 2178

Wenn Sie bash 4.2 oder höher zur Verfügung haben, könnte das Folgende für Sie nützlich 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 könnte, wenn Ihre Aufgaben viel CPU- oder E/A-Zeit verbrauchen und Sie die Anzahl der gleichzeitigen Aufgaben begrenzen wollen.

Das Skript startet alle Aufgaben in der ersten Schleife und verbraucht die Ergebnisse in der zweiten Schleife.

Das ist für einfache Fälle ein bisschen übertrieben, aber es ermöglicht ziemlich tolle Sachen. Zum Beispiel kann man Fehlermeldungen für jede Aufgabe in einem anderen assoziativen Array speichern und sie ausgeben, nachdem sich alles beruhigt hat.

#! /bin/bash

main () {
    local -A pids=()
    local -A tasks=([task1]="echo 1"
                    [task2]="echo 2"
                    [task3]="echo 3"
                    [task4]="false"
                    [task5]="echo 5"
                    [task6]="false")
    local max_concurrent_tasks=2

    for key in "${!tasks[@]}"; do
        while [ $(jobs 2>&1 | grep -c Running) -ge "$max_concurrent_tasks" ]; do
            sleep 1 # gnu sleep allows floating point here...
        done
        ${tasks[$key]} &
        pids+=(["$key"]="$!")
    done

    errors=0
    for key in "${!tasks[@]}"; do
        pid=${pids[$key]}
        local cur_ret=0
        if [ -z "$pid" ]; then
            echo "No Job ID known for the $key process" # should never happen
            cur_ret=1
        else
            wait $pid
            cur_ret=$?
        fi
        if [ "$cur_ret" -ne 0 ]; then
            errors=$(($errors + 1))
            echo "$key (${tasks[$key]}) failed."
        fi
    done

    return $errors
}

main

5voto

estani Punkte 20703

Speichern Sie die Ergebnisse einfach außerhalb der Shell, z.B. in einer Datei.

#!/bin/bash
tmp=/tmp/results

: > $tmp  #clean the file

for i in `seq 0 9`; do
  (doCalculations $i; echo $i:$?>>$tmp)&
done      #iterate

wait      #wait until all ready

sort $tmp | grep -v ':0'  #... handle as required

4voto

Tosh Punkte 51

Ich habe gerade ein Skript modifiziert, um einen Prozess zu parallelisieren.

Ich habe ein wenig experimentiert (auf Solaris sowohl mit bash als auch mit ksh) und entdeckt, dass 'wait' den Exit-Status ausgibt, wenn er nicht Null ist, oder eine Liste von Jobs, die einen Exit ungleich Null liefern, wenn kein PID-Argument angegeben wird. Z.B..

Bash:

$ sleep 20 && exit 1 &
$ sleep 10 && exit 2 &
$ wait
[1]-  Exit 2                  sleep 20 && exit 2
[2]+  Exit 1                  sleep 10 && exit 1

Ksh:

$ sleep 20 && exit 1 &
$ sleep 10 && exit 2 &
$ wait
[1]+  Done(2)                  sleep 20 && exit 2
[2]+  Done(1)                  sleep 10 && exit 1

Diese Ausgabe wird nach stderr geschrieben, so dass eine einfache Lösung für das Beispiel des Autors sein könnte:

#!/bin/bash

trap "rm -f /tmp/x.$$" EXIT

for i in `seq 0 9`; do
  doCalculations $i &
done

wait 2> /tmp/x.$$
if [ `wc -l /tmp/x.$$` -gt 0 ] ; then
  exit 1
fi

Während dies:

wait 2> >(wc -l)

gibt ebenfalls eine Zählung zurück, allerdings ohne die tmp-Datei. Dies kann z.B. auch so verwendet werden:

wait 2> >(if [ `wc -l` -gt 0 ] ; then echo "ERROR"; fi)

Aber das ist IMO nicht viel nützlicher als die tmp-Datei. Ich konnte keinen nützlichen Weg finden, um die tmp-Datei zu vermeiden und gleichzeitig das Ausführen von "wait" in einer Subshell zu vermeiden, was überhaupt nicht funktionieren würde.

3voto

Alexander Mills Punkte 76733

Das funktioniert, sollte genauso gut, wenn nicht sogar besser sein als die Antwort von @HoverHell!

#!/usr/bin/env bash

set -m # allow for job control
EXIT_CODE=0;  # exit code of overall script

function foo() {
     echo "CHLD exit code is $1"
     echo "CHLD pid is $2"
     echo $(jobs -l)

     for job in `jobs -p`; do
         echo "PID => ${job}"
         wait ${job} ||  echo "At least one test failed with exit code => $?" ; EXIT_CODE=1
     done
}

trap 'foo $? $$' CHLD

DIRN=$(dirname "$0");

commands=(
    "{ echo "foo" && exit 4; }"
    "{ echo "bar" && exit 3; }"
    "{ echo "baz" && exit 5; }"
)

clen=`expr "${#commands[@]}" - 1` # get length of commands - 1

for i in `seq 0 "$clen"`; do
    (echo "${commands[$i]}" | bash) &   # run the command via bash in subshell
    echo "$i ith command has been issued as a background job"
done

# wait for all to finish
wait;

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"

# end

und natürlich habe ich dieses Skript in einem NPM-Projekt verewigt, das es erlaubt, Bash-Befehle parallel auszuführen, was für Tests nützlich ist:

https://github.com/ORESoftware/generic-subshell

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X