Bash-Skript warten auf Prozesse und erhalten Rückkehrcode

13

Ich versuche ein Skript zu erstellen, das viele Hintergrundbefehle startet. Für jeden Hintergrundbefehl muss ich den Rückkehrcode erhalten.

Ich habe folgendes Skript ausprobiert:

 #!/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]}

Mein Problem ist während der Warteschleife. Wenn die zweite PID vor der ersten endet, kann ich den Rückkehrcode nicht erhalten.

Ich weiß, dass ich wait pid1 pid2 ausführen kann, aber mit diesem Befehl kann ich nicht den Rückkehrcode aller Befehle erhalten.

Irgendeine Idee ?

Hugo
quelle

Antworten:

6

Sie können dies tun, indem Sie ein temporäres Verzeichnis verwenden.

# 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
quelle
9

Das Problem ist mehr mit Ihrem

for i in ${#pid[@]}

Welches ist for i in 2.

Es sollte eher sein:

for i in 1 2

oder

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

wait "$pid" gibt den Exit-Code des Jobs mit bash(und POSIX-Shells, aber nicht zsh) zurück, auch wenn der Job beim waitStart bereits beendet wurde.

Stéphane Chazelas
quelle
5

Eine generische Implementierung ohne temporäre Dateien.

#!/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
quelle
Danke :-) Genau das habe ich gesucht!
Qorbani
0

Stéphanes Antwort ist gut, aber ich würde es vorziehen

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

Dies wird über die Schlüssel des pidArrays iterieren , unabhängig davon, welche Einträge noch vorhanden sind, sodass Sie es anpassen, aus der Schleife ausbrechen und die gesamte Schleife neu starten können, und es funktioniert einfach. Und Sie brauchen zunächst keine aufeinanderfolgenden Werte von i.

Wenn Sie mit Tausenden von Prozessen zu tun haben, ist der Ansatz von Stépane möglicherweise etwas effizienter, wenn Sie eine nicht spärliche Liste haben.

Martin Kealey
quelle