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

3voto

Ente Punkte 2125

Genau für diesen Zweck habe ich ein bash Funktion genannt :for .

Nota : :for behält nicht nur den Exit-Code der fehlgeschlagenen Funktion bei und gibt ihn zurück, sondern beendet auch alle parallel laufenden Instanzen. Was in diesem Fall vielleicht nicht nötig ist.

#!/usr/bin/env bash

# Wait for pids to terminate. If one pid exits with
# a non zero exit code, send the TERM signal to all
# processes and retain that exit code
#
# usage:
# :wait 123 32
function :wait(){
    local pids=("$@")
    [ ${#pids} -eq 0 ] && return $?

    trap 'kill -INT "${pids[@]}" &>/dev/null || true; trap - INT' INT
    trap 'kill -TERM "${pids[@]}" &>/dev/null || true; trap - RETURN TERM' RETURN TERM

    for pid in "${pids[@]}"; do
        wait "${pid}" || return $?
    done

    trap - INT RETURN TERM
}

# Run a function in parallel for each argument.
# Stop all instances if one exits with a non zero
# exit code
#
# usage:
# :for func 1 2 3
#
# env:
# FOR_PARALLEL: Max functions running in parallel
function :for(){
    local f="${1}" && shift

    local i=0
    local pids=()
    for arg in "$@"; do
        ( ${f} "${arg}" ) &
        pids+=("$!")
        if [ ! -z ${FOR_PARALLEL+x} ]; then
            (( i=(i+1)%${FOR_PARALLEL} ))
            if (( i==0 )) ;then
                :wait "${pids[@]}" || return $?
                pids=()
            fi
        fi
    done && [ ${#pids} -eq 0 ] || :wait "${pids[@]}" || return $?
}

Verwendung

for.sh :

#!/usr/bin/env bash
set -e

# import :for from gist: https://gist.github.com/Enteee/c8c11d46a95568be4d331ba58a702b62#file-for
# if you don't like curl imports, source the actual file here.
source <(curl -Ls https://gist.githubusercontent.com/Enteee/c8c11d46a95568be4d331ba58a702b62/raw/)

msg="You should see this three times"

:(){
  i="${1}" && shift

  echo "${msg}"

  sleep 1
  if   [ "$i" == "1" ]; then sleep 1
  elif [ "$i" == "2" ]; then false
  elif [ "$i" == "3" ]; then
    sleep 3
    echo "You should never see this"
  fi
} && :for : 1 2 3 || exit $?

echo "You should never see this"

$ ./for.sh; echo $?
You should see this three times
You should see this three times
You should see this three times
1

Referenzen

3voto

FreelanceConsultant Punkte 10989

Es gibt bereits eine Menge Antworten hier, aber ich bin überrascht, niemand scheint zu haben vorgeschlagen, mit Arrays... Also hier ist, was ich tat - dies könnte nützlich sein, einige in der Zukunft.

n=10 # run 10 jobs
c=0
PIDS=()

while true

    my_function_or_command &
    PID=$!
    echo "Launched job as PID=$PID"
    PIDS+=($PID)

    (( c+=1 ))

    # required to prevent any exit due to error
    # caused by additional commands run which you
    # may add when modifying this example
    true

do

    if (( c < n ))
    then
        continue
    else
        break
    fi
done 

# collect launched jobs

for pid in "${PIDS[@]}"
do
    wait $pid || echo "failed job PID=$pid"
done

3voto

troelskn Punkte 110542

Ich brauchte dies, aber der Zielprozess war kein Kind der aktuellen Shell, in diesem Fall wait $PID funktioniert nicht. Ich habe stattdessen die folgende Alternative gefunden:

while [ -e /proc/$PID ]; do sleep 0.1 ; done

Voraussetzung dafür ist das Vorhandensein von procfs die möglicherweise nicht verfügbar ist (Mac bietet sie zum Beispiel nicht an). Aus Gründen der Portabilität könnten Sie stattdessen dies verwenden:

while ps -p $PID >/dev/null ; do sleep 0.1 ; done

2voto

Yajo Punkte 4768
set -e
fail () {
    touch .failure
}
expect () {
    wait
    if [ -f .failure ]; then
        rm -f .failure
        exit 1
    fi
}

sleep 2 || fail &
sleep 2 && false || fail &
sleep 2 || fail
expect

El set -e am Anfang lässt Ihr Skript bei einem Fehler anhalten.

expect wird zurückgegeben 1 wenn ein Unterauftrag fehlgeschlagen ist.

2voto

Anju Prasannan Punkte 41

Es kann der Fall eintreten, dass der Vorgang abgeschlossen ist, bevor man auf den Vorgang wartet. Wenn wir das Warten auf einen Prozess auslösen, der bereits beendet ist, wird ein Fehler wie pid ist kein Kind dieser Shell ausgelöst. Um solche Fälle zu vermeiden, kann die folgende Funktion verwendet werden, um festzustellen, ob der Prozess abgeschlossen ist oder nicht:

isProcessComplete(){
PID=$1
while [ -e /proc/$PID ]
do
    echo "Process: $PID is still running"
    sleep 5
done
echo "Process $PID has finished"
}

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