Question Comment attendre plusieurs sous-processus dans bash pour retourner le code de sortie! = 0 quand un sous-processus se termine avec le code! = 0?


Comment attendre dans un script bash plusieurs sous-processus issus de ce script pour terminer et renvoyer le code de sortie! = 0 lorsque l'un des sous-processus se termine par le code! = 0?

Script simple:

#!/bin/bash
for i in `seq 0 9`; do
  doCalculations $i &
done
wait

Le script ci-dessus attendra les 10 sous-processus générés, mais il donnera toujours le statut de sortie 0 (voir help wait). Comment puis-je modifier ce script pour qu'il détecte les états de sortie des sous-processus générés et renvoie le code de sortie 1 lorsque l'un des sous-processus se termine par le code! = 0?

Y a-t-il une meilleure solution pour cela que de collecter les PID des sous-processus, les attendre dans l'ordre et les statuts de sortie?


416
2017-12-10 13:54


origine


Réponses:


wait aussi (optionnel) prend le PID du processus à attendre, et avec $! vous obtenez le PID de la dernière commande lancée en arrière-plan. Modifiez la boucle pour stocker le PID de chaque sous-processus engendré dans un tableau, puis bouclez à nouveau en attente sur chaque PID.

# run processes and store pids in array
for i in $n_procs; do
    ./procs[${i}] &
    pids[${i}]=$!
done

# wait for all pids
for pid in ${pids[*]}; do
    wait $pid
done

366
2017-12-10 14:07



http://jeremy.zawodny.com/blog/archives/010717.html :

#!/bin/bash

FAIL=0

echo "starting"

./sleeper 2 0 &
./sleeper 2 1 &
./sleeper 3 0 &
./sleeper 2 0 &

for job in `jobs -p`
do
echo $job
    wait $job || let "FAIL+=1"
done

echo $FAIL

if [ "$FAIL" == "0" ];
then
echo "YAY!"
else
echo "FAIL! ($FAIL)"
fi

255
2018-02-05 09:36



Si vous avez installé GNU Parallel, vous pouvez faire:

# If doCalculations is a function
export -f doCalculations
seq 0 9 | parallel doCalculations {}

GNU Parallel vous donnera le code de sortie:

  • 0 - Toutes les tâches ont été exécutées sans erreur.

  • 1-253 - Certains travaux ont échoué. Le statut de sortie indique le nombre de travaux échoués

  • 254 - Plus de 253 travaux ont échoué.

  • 255 - Autre erreur.

Regardez les vidéos d'introduction pour en savoir plus: http://pi.dk/1


44
2018-02-16 12:16



Voici ce que j'ai trouvé jusqu'à présent. Je voudrais voir comment interrompre la commande de sommeil si un enfant se termine, de sorte que l'on ne devrait pas accorder WAITALL_DELAY à son usage.

waitall() { # PID...
  ## Wait for children to exit and indicate whether all exited with 0 status.
  local errors=0
  while :; do
    debug "Processes remaining: $*"
    for pid in "$@"; do
      shift
      if kill -0 "$pid" 2>/dev/null; then
        debug "$pid is still alive."
        set -- "$@" "$pid"
      elif wait "$pid"; then
        debug "$pid exited with zero exit status."
      else
        debug "$pid exited with non-zero exit status."
        ((++errors))
      fi
    done
    (("$#" > 0)) || break
    # TODO: how to interrupt this sleep when a child terminates?
    sleep ${WAITALL_DELAY:-1}
   done
  ((errors == 0))
}

debug() { echo "DEBUG: $*" >&2; }

pids=""
for t in 3 5 4; do 
  sleep "$t" &
  pids="$pids $!"
done
waitall $pids

38
2017-10-07 16:05



Que diriez-vous simplement de:

#!/bin/bash

pids=""

for i in `seq 0 9`; do
   doCalculations $i &
   pids="$pids $!"
done

wait $pids

...code continued here ...

Mettre à jour:

Comme indiqué par plusieurs commentateurs, ce qui précède attend que tous les processus soient terminés avant de continuer, mais ne se termine pas et échoue si l’un d’eux échoue. :

#!/bin/bash

pids=""
RESULT=0


for i in `seq 0 9`; do
   doCalculations $i &
   pids="$pids $!"
done

for pid in $pids; do
    wait $pid || let "RESULT=1"
done

if [ "$RESULT" == "1" ];
    then
       exit 1
fi

...code continued here ...

31
2018-03-16 14:12



Voici un exemple simple utilisant wait.

Exécuter des processus:

$ sleep 10 &
$ sleep 10 &
$ sleep 20 &
$ sleep 20 &

Alors attends les avec wait commander:

$ wait < <(jobs -p)

Ou juste wait (sans arguments) pour tous.

Cela attendra que tous les travaux en arrière-plan soient terminés.

Si la -n L'option est fournie, attend la fin du travail suivant et renvoie son statut de sortie.

Voir: help wait et help jobs pour la syntaxe.

Cependant, l'inconvénient est que cela ne reviendra que sur le statut du dernier identifiant. Vous devez donc vérifier l'état de chaque sous-processus et le stocker dans la variable.

Ou faites votre fonction de calcul pour créer un fichier en cas d'échec (vide ou avec un journal d'échec), puis contrôlez ce fichier s'il existe, par ex.

$ sleep 20 && true || tee fail &
$ sleep 20 && false || tee fail &
$ wait < <(jobs -p)
$ test -f fail && echo Calculation failed.

20
2018-06-13 05:14



Pour paralléliser cela ...

for i in $(whatever_list) ; do
   do_something $i
done

Traduisez ça en ...

for i in $(whatever_list) ; do echo $i ; done | ## execute in parallel...
   (
   export -f do_something ## export functions (if needed)
   export PATH ## export any variables that are required
   xargs -I{} --max-procs 0 bash -c ' ## process in batches...
      {
      echo "processing {}" ## optional
      do_something {}
      }' 
   )
  • Si une erreur survient dans un processus, il n'interrompra pas les autres processus, mais il en résultera un code de sortie différent de zéro de la séquence dans son ensemble.
  • L'exportation de fonctions et de variables peut ou non être nécessaire, dans un cas particulier.
  • Vous pouvez définir --max-procs basé sur combien de parallélisme vous voulez (0signifie "tout à la fois").
  • GNU Parallel offre des fonctionnalités supplémentaires lorsqu'il est utilisé à la place de xargs - mais ce n’est pas toujours installé par défaut.
  • le for la boucle n'est pas strictement nécessaire dans cet exemple puisque echo $i est simplement en train de régénérer la sortie de $(whatever_list). Je pense juste que l'utilisation du for mot-clé, il est un peu plus facile de voir ce qui se passe.
  • La gestion des chaînes de Bash peut être déroutante - j'ai trouvé que l'utilisation de guillemets simples était plus adaptée à l'encapsulation de scripts non triviaux.
  • Vous pouvez facilement interrompre toute l'opération (en utilisant ^ C ou similaire), contrairement à l'approche plus directe du parallélisme Bash.

Voici un exemple de travail simplifié ...

for i in {0..5} ; do echo $i ; done |xargs -I{} --max-procs 2 bash -c '
   {
   echo sleep {}
   sleep 2s
   }'

17
2018-02-24 21:35



Je vois beaucoup de bons exemples énumérés ici, je voulais aussi jeter le mien.

#! /bin/bash

items="1 2 3 4 5 6"
pids=""

for item in $items; do
    sleep $item &
    pids+="$! "
done

for pid in $pids; do
    wait $pid
    if [ $? -eq 0 ]; then
        echo "SUCCESS - Job $pid exited with a status of $?"
    else
        echo "FAILED - Job $pid exited with a status of $?"
    fi
done

J'utilise quelque chose de très similaire pour démarrer / arrêter des serveurs / services en parallèle et vérifier chaque état de sortie. Fonctionne très bien pour moi. J'espère que cela aide quelqu'un!


7
2017-12-10 14:28



Je ne crois pas que cela soit possible avec la fonctionnalité intégrée de Bash.

Toi pouvez recevoir une notification lorsqu'un enfant quitte:

#!/bin/sh
set -o monitor        # enable script job control
trap 'echo "child died"' CHLD

Cependant, il n'existe aucun moyen apparent d'obtenir le statut de sortie de l'enfant dans le gestionnaire de signaux.

Obtenir le statut de cet enfant est généralement le travail du wait famille de fonctions dans les API POSIX de niveau inférieur. Malheureusement, le soutien de Bash est limité - vous pouvez attendre un processus enfant spécifique (et obtenir son statut de sortie) ou vous pouvez attendre pour tout d'entre eux, et toujours obtenir un résultat 0.

Ce qu'il semble impossible de faire est l'équivalent de waitpid(-1), qui bloque jusqu'à tout retours de processus enfant.


6
2017-10-03 19:51



Le code suivant attend la fin de tous les calculs et renvoie le statut de sortie 1 si l'un des doCalculations échoue.

#!/bin/bash
for i in $(seq 0 9); do
   (doCalculations $i >&2 & wait %1; echo $?) &
done | grep -qv 0 && exit 1

5
2017-08-03 23:36