Le script Bash attend les processus et obtient le code retour

13

J'essaie de créer un script qui lancera de nombreuses commandes d'arrière-plan. Pour chaque commande d'arrière-plan, j'ai besoin d'obtenir le code retour.

J'ai essayé le script suivant:

 #!/bin/bash
set -x
pid=()
return=()


for i in 1 2
do
 echo start $i
 ssh mysql "/root/test$i.sh" &
 pid[$i]=$!
done

for i in ${#pid[@]}
do
echo ${pid[$i]}
wait ${pid[$i]}
return[$i]=$?

if [ ${return[$i]} -ne 0 ]
then
  echo mail error
fi

done

echo ${return[1]}
echo ${return[2]}

Mon problème est pendant la boucle d'attente, si le deuxième pid se termine avant le premier, je ne pourrai pas obtenir le code retour.

Je sais que je peux exécuter wait pid1 pid2, mais avec cette commande, je ne peux pas obtenir le code retour de toutes les commandes.

Une idée ?

Hugo
la source

Réponses:

6

Vous pouvez le faire en utilisant un répertoire temporaire.

# Create a temporary directory to store the statuses
dir=$(mktemp -d)

# Execute the backgrouded code. Create a file that contains the exit status.
# The filename is the PID of this group's subshell.
for i in 1 2; do
    { ssh mysql "/root/test$i.sh" ; echo "$?" > "$dir/$BASHPID" ; } &
done

# Wait for all jobs to complete
wait

# Get return information for each pid
for file in "$dir"/*; do
    printf 'PID %d returned %d\n' "${file##*/}" "$(<"$file")"
done

# Remove the temporary directory
rm -r "$dir"
Chris Down
la source
9

Le problème est plus avec votre

for i in ${#pid[@]}

Ce qui est for i in 2.

Il faudrait plutôt:

for i in 1 2

ou

for ((i = 1; i <= ${#pid[@]}; i++))

wait "$pid" va retourner le code de sortie du travail avec bash(et coquilles POSIX, mais pas zsh) , même si le travail avait déjà terminé quand waita commencé.

Stéphane Chazelas
la source
5

Une implémentation générique sans fichiers temporaires.

#!/usr/bin/env bash

## associative array for job status
declare -A JOBS

## run command in the background
background() {
  eval $1 & JOBS[$!]="$1"
}

## check exit status of each job
## preserve exit status in ${JOBS}
## returns 1 if any job failed
reap() {
  local cmd
  local status=0
  for pid in ${!JOBS[@]}; do
    cmd=${JOBS[${pid}]}
    wait ${pid} ; JOBS[${pid}]=$?
    if [[ ${JOBS[${pid}]} -ne 0 ]]; then
      status=${JOBS[${pid}]}
      echo -e "[${pid}] Exited with status: ${status}\n${cmd}"
    fi
  done
  return ${status}
}

background 'sleep 1 ; false'
background 'sleep 3 ; true'
background 'sleep 2 ; exit 5'
background 'sleep 5 ; true'

reap || echo "Ooops! Some jobs failed"
Jon Nalley
la source
Merci :-) C'est exactement ce que je cherchais!
Qorbani
0

La réponse de Stéphane est bonne, mais je préférerais

for i in ${!pid[@]}
do
    wait ${pid[i]}
    return[i]=$?
    unset "pid[$i]"
done

qui itérera sur les clés du pidtableau, quelles que soient les entrées qui existent encore, vous pouvez donc l'adapter, sortir de la boucle et redémarrer la boucle entière et cela fonctionnera. Et vous n'avez pas besoin de valeurs consécutives de ipour commencer.

Bien sûr, si vous avez affaire à des milliers de processus, l'approche de Stépane serait peut-être légèrement plus efficace lorsque vous avez une liste non clairsemée.

Martin Kealey
la source