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 ?

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"

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é.


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"

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.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.