it-swarm.com.de

Sammeln Sie Exit-Codes paralleler Hintergrundprozesse (Sub-Shells).

Angenommen, wir haben ein Bash-Skript wie folgt:

echo "x" &
echo "y" &
echo "z" &
.....
echo "Z" &
wait

gibt es eine Möglichkeit, die Exit-Codes der Sub-Shells/Sub-Prozesse zu erfassen? Suche nach einem Weg, dies zu tun und kann nichts finden. Ich muss diese Subshells parallel ausführen, sonst wäre das ja einfacher.

Ich suche nach einer generischen Lösung (ich habe eine unbekannte/dynamische Anzahl von Unterprozessen, die parallel ausgeführt werden sollen).

19
Alexander Mills

Die Antwort von Alexander Mills, der handleJobs verwendet, gab mir einen guten Ausgangspunkt, gab mir aber auch diesen Fehler

warnung: run_pending_traps: Ungültiger Wert in trap_list [17]: 0x461010

Dies kann ein Problem mit den Bash-Race-Bedingungen sein

Stattdessen habe ich nur die PID jedes Kindes gespeichert und gewartet und den Exit-Code für jedes Kind speziell erhalten. Ich finde das sauberer in Bezug auf Unterprozesse, die Unterprozesse in Funktionen erzeugen und das Risiko vermeiden, auf einen übergeordneten Prozess zu warten, bei dem ich auf ein Kind warten wollte. Es ist klarer, was passiert, weil es die Falle nicht benutzt.

#!/usr/bin/env bash

# it seems it does not work well if using echo for function return value, and calling inside $() (is a subprocess spawned?) 
function wait_and_get_exit_codes() {
    children=("[email protected]")
    EXIT_CODE=0
    for job in "${children[@]}"; do
       echo "PID => ${job}"
       CODE=0;
       wait ${job} || CODE=$?
       if [[ "${CODE}" != "0" ]]; then
           echo "At least one test failed with exit code => ${CODE}" ;
           EXIT_CODE=1;
       fi
   done
}

DIRN=$(dirname "$0");

commands=(
    "{ echo 'a'; exit 1; }"
    "{ echo 'b'; exit 0; }"
    "{ echo 'c'; exit 2; }"
    )

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

children_pids=()
for i in `seq 0 "$clen"`; do
    (echo "${commands[$i]}" | bash) &   # run the command via bash in subshell
    children_pids+=("$!")
    echo "$i ith command has been issued as a background job"
done
# wait; # wait for all subshells to finish - its still valid to wait for all jobs to finish, before processing any exit-codes if we wanted to
#EXIT_CODE=0;  # exit code of overall script
wait_and_get_exit_codes "${children_pids[@]}"

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"
# end
6
arberg

Verwenden Sie wait mit einer PID, die wird:

Warten Sie, bis der untergeordnete Prozess, der in jeder Prozess-ID angegeben ist, pid oder Jobspezifikation jobspec beendet und gibt den Exit-Status des zuletzt warteten Befehls zurück.

Sie müssen die PID jedes Prozesses speichern, während Sie fortfahren:

echo "x" & X=$!
echo "y" & Y=$!
echo "z" & Z=$!

Sie können die Jobsteuerung im Skript auch mit set -m Aktivieren und eine %n - Jobspezifikation verwenden, möchten aber mit ziemlicher Sicherheit nicht - die Jobsteuerung hat viele andere Nebenwirkungen .

wait gibt denselben Code zurück, mit dem der Vorgang abgeschlossen wurde. Sie können wait $X Zu einem beliebigen (angemessenen) späteren Zeitpunkt verwenden, um auf den endgültigen Code als $? Zuzugreifen, oder ihn einfach als true/false verwenden:

echo "x" & X=$!
echo "y" & Y=$!
...
wait $X
echo "job X returned $?"

wait wird angehalten, bis der Befehl abgeschlossen ist, falls dies noch nicht geschehen ist.

Wenn Sie ein solches Abwürgen vermeiden möchten, können Sie setzen Sie ein trap auf SIGCHLD , zählen Sie die Anzahl der Abbrüche und behandeln Sie alle waits sofort, wenn sie alle fertig sind. Sie können wahrscheinlich fast die ganze Zeit mit wait alleine davonkommen.

21
Michael Homer

Wenn Sie eine gute Möglichkeit hatten, die Befehle zu identifizieren, können Sie ihren Exit-Code in eine tmp-Datei drucken und dann auf die gewünschte Datei zugreifen:

#!/bin/bash

for i in `seq 1 5`; do
    ( sleep $i ; echo $? > /tmp/cmd__${i} ) &
done

wait

for i in `seq 1 5`; do # or even /tmp/cmd__*
    echo "process $i:"
    cat /tmp/cmd__${i}
done

Vergessen Sie nicht, die tmp-Dateien zu entfernen.

5
Rolf

Benutze einen compound command - Setzen Sie die Anweisung in Klammern:

( echo "x" ; echo X: $? ) &
( true ; echo TRUE: $? ) &
( false ; echo FALSE: $? ) &

gibt die Ausgabe

x
X: 0
TRUE: 0
FALSE: 1

Eine ganz andere Möglichkeit, mehrere Befehle parallel auszuführen, ist die Verwendung von GNU Parallel . Erstellen Sie eine Liste der auszuführenden Befehle und fügen Sie sie in die Datei list ein:

cat > list
sleep 2 ; exit 7
sleep 3 ; exit 55
^D

Führen Sie alle Befehle parallel aus und sammeln Sie die Exit-Codes in der Datei job.log:

cat list | parallel -j0 --joblog job.log
cat job.log

und die Ausgabe ist:

Seq     Host    Starttime       JobRuntime      Send    Receive Exitval Signal  Command
1       :       1486892487.325       1.976      0       0       7       0       sleep 2 ; exit 7
2       :       1486892487.326       3.003      0       0       55      0       sleep 3 ; exit 55
4
hschou

dies ist das generische Skript, nach dem Sie suchen. Der einzige Nachteil ist, dass Ihre Befehle in Anführungszeichen stehen, was bedeutet, dass die Syntaxhervorhebung über Ihre IDE nicht wirklich funktioniert. Ansonsten habe ich einige der anderen Antworten ausprobiert und dies ist die beste. Diese Antwort beinhaltet die Idee der Verwendung von wait <pid> von @Michael gegeben, geht aber noch einen Schritt weiter, indem der Befehl trap verwendet wird, der am besten zu funktionieren scheint.

#!/usr/bin/env bash

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

function handleJobs() {
     for job in `jobs -p`; do
         echo "PID => ${job}"
         CODE=0;
         wait ${job} || CODE=$?
         if [[ "${CODE}" != "0" ]]; then
         echo "At least one test failed with exit code => ${CODE}" ;
         EXIT_CODE=1;
         fi
     done
}

trap 'handleJobs' CHLD  # trap command is the key part
DIRN=$(dirname "$0");

commands=(
    "{ echo 'a'; exit 1; }"
    "{ echo 'b'; exit 0; }"
    "{ echo 'c'; exit 2; }"
)

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; # wait for all subshells to finish

echo "EXIT_CODE => $EXIT_CODE"
exit "$EXIT_CODE"
# end

vielen Dank an @michael homer, der mich auf den richtigen Weg gebracht hat, aber die Verwendung des Befehls trap ist der beste Ansatz für AFAICT.

2
Alexander Mills

Eine weitere Variation von @rolfs Antwort:

Eine andere Möglichkeit, den Exit-Status zu speichern, wäre etwa

mkdir /tmp/status_dir

und dann jedes Skript haben

script_name="${0##*/}"  ## strip path from script name
tmpfile="/tmp/status_dir/${script_name}.$$"
do something
rc=$?
echo "$rc" > "$tmpfile"

Auf diese Weise erhalten Sie für jede Statusdatei einen eindeutigen Namen, einschließlich des Namens des Skripts, das sie erstellt hat, und seiner Prozess-ID (falls mehr als eine Instanz desselben Skripts ausgeführt wird), die Sie später als Referenz speichern und in die Datei einfügen können Am selben Ort können Sie einfach das gesamte Unterverzeichnis löschen, wenn Sie fertig sind.

Sie können sogar mehr als einen Status aus jedem Skript speichern, indem Sie so etwas tun

tmpfile="$(/bin/mktemp -q "/tmp/status_dir/${script_name}.$$.XXXXXX")"

dadurch wird die Datei wie zuvor erstellt, aber eine eindeutige zufällige Zeichenfolge hinzugefügt.

Oder Sie können einfach weitere Statusinformationen an dieselbe Datei anhängen.

1
Joe

script3 wird nur ausgeführt, wenn script1 und script2 sind erfolgreich und script1 und script2 wird parallel ausgeführt:

./script1 &
process1=$!

./script2 &
process2=$!

wait $process1
rc1=$?

wait $process2
rc2=$?

if [[ $rc1 -eq 0 ]] && [[ $rc2 -eq 0  ]];then
./script3
fi
1
venkata