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

2voto

Daniel C. Sobral Punkte 290004

Ich wäre fast in die Falle getappt, die jobs -p um PIDs zu sammeln, was nicht funktioniert, wenn das Kind bereits beendet wurde, wie im Skript unten gezeigt. Die Lösung, die ich gewählt habe, war der einfache Aufruf von wait -n N-mal, wobei N die Anzahl der Kinder ist, die ich habe und die ich zufällig deterministisch kenne.

#!/usr/bin/env bash

sleeper() {
    echo "Sleeper $1"
    sleep $2
    echo "Exiting $1"
    return $3
}

start_sleepers() {
    sleeper 1 1 0 &
    sleeper 2 2 $1 &
    sleeper 3 5 0 &
    sleeper 4 6 0 &
    sleep 4
}

echo "Using jobs"
start_sleepers 1

pids=( $(jobs -p) )

echo "PIDS: ${pids[*]}"

for pid in "${pids[@]}"; do
    wait "$pid"
    echo "Exit code $?"
done

echo "Clearing other children"
wait -n; echo "Exit code $?"
wait -n; echo "Exit code $?"

echo "Waiting for N processes"
start_sleepers 2

for ignored in $(seq 1 4); do
    wait -n
    echo "Exit code $?"
done

Ausgabe:

Using jobs
Sleeper 1
Sleeper 2
Sleeper 3
Sleeper 4
Exiting 1
Exiting 2
PIDS: 56496 56497
Exiting 3
Exit code 0
Exiting 4
Exit code 0
Clearing other children
Exit code 0
Exit code 1
Waiting for N processes
Sleeper 1
Sleeper 2
Sleeper 3
Sleeper 4
Exiting 1
Exiting 2
Exit code 0
Exit code 2
Exiting 3
Exit code 0
Exiting 4
Exit code 0

2voto

Maxxim Punkte 2069

Beginnend mit Bash 5.1 gibt es dank der Einführung von wait -p :

#!/usr/bin/env bash

# Spawn background jobs
for ((i=0; i < 10; i++)); do
    secs=$((RANDOM % 10)); code=$((RANDOM % 256))
    (sleep ${secs}; exit ${code}) &
    echo "Started background job (pid: $!, sleep: ${secs}, code: ${code})"
done

# Wait for background jobs, print individual results, determine overall result
result=0
while true; do
    wait -n -p pid; code=$?
    [[ -z "${pid}" ]] && break
    echo "Background job ${pid} finished with code ${code}"
    (( ${code} != 0 )) && result=1
done

# Return overall result
exit ${result}

1voto

Lloeki Punkte 6283

Ich habe dies kürzlich benutzt (dank Alnitak):

#!/bin/bash
# activate child monitoring
set -o monitor

# locking subprocess
(while true; do sleep 0.001; done) &
pid=$!

# count, and kill when all done
c=0
function kill_on_count() {
    # you could kill on whatever criterion you wish for
    # I just counted to simulate bash's wait with no args
    [ $c -eq 9 ] && kill $pid
    c=$((c+1))
    echo -n '.' # async feedback (but you don't know which one)
}
trap "kill_on_count" CHLD

function save_status() {
    local i=$1;
    local rc=$2;
    # do whatever, and here you know which one stopped
    # but remember, you're called from a subshell
    # so vars have their values at fork time
}

# care must be taken not to spawn more than one child per loop
# e.g don't use `seq 0 9` here!
for i in {0..9}; do
    (doCalculations $i; save_status $i $?) &
done

# wait for locking subprocess to be killed
wait $pid
echo

Von dort aus kann man leicht extrapolieren und einen Auslöser haben (eine Datei berühren, ein Signal senden) und die Zählkriterien ändern (berührte Dateien zählen, oder was auch immer), um auf diesen Auslöser zu reagieren. Oder, wenn Sie nur 'jede' nicht Null rc wollen, einfach die Sperre von save_status beenden.

1voto

vishnuitta Punkte 21

Die Lösung, um auf mehrere Unterprozesse zu warten und sich zu beenden, wenn einer von ihnen mit einem Statuscode ungleich Null beendet wird, ist die Verwendung von 'wait -n'

#!/bin/bash
wait_for_pids()
{
    for (( i = 1; i <= $#; i++ )) do
        wait -n $@
        status=$?
        echo "received status: "$status
        if [ $status -ne 0 ] && [ $status -ne 127 ]; then
            exit 1
        fi
    done
}

sleep_for_10()
{
    sleep 10
    exit 10
}

sleep_for_20()
{
    sleep 20
}

sleep_for_10 &
pid1=$!

sleep_for_20 &
pid2=$!

wait_for_pids $pid2 $pid1

Der Statuscode "127" steht für einen nicht existierenden Prozess, was bedeutet, dass das Kind möglicherweise beendet wurde.

1voto

Paul Hodges Punkte 31

Die Falle ist dein Freund. In vielen Systemen können Sie bei ERR trappen. Sie können EXIT oder DEBUG trappen, um nach jedem Befehl ein Stück Code auszuführen.

Dies zusätzlich zu allen Standardsignalen.

modifier

Ich habe mich versehentlich mit einem falschen Konto angemeldet, daher hatte ich die Anfrage nach Beispielen nicht gesehen.

Versuchen Sie es hier, auf meinem regulären Konto.

Behandlung von Ausnahmen in Bash-Skripten

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