La secuencia de comandos Bash espera los procesos y obtiene el código de retorno

13

Estoy tratando de crear un script que iniciará muchos comandos de fondo. Para cada comando de fondo, necesito obtener el código de retorno.

He estado probando el siguiente script:

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

Mi problema es durante el ciclo de espera, si el segundo pid termina antes del primero, no podré obtener el código de retorno.

Sé que puedo ejecutar wait pid1 pid2, pero con este comando no puedo obtener el código de retorno de todos los comandos.

Alguna idea ?

Hugo
fuente

Respuestas:

6

Puede hacer esto usando un directorio temporal.

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

El problema es más con su

for i in ${#pid[@]}

Lo cual es for i in 2.

Más bien debería ser:

for i in 1 2

o

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

wait "$pid" se devolverá el código de salida del trabajo con bash(y conchas POSIX, pero no zsh) incluso si el trabajo ya había terminado cuando waitse inició.

Stéphane Chazelas
fuente
5

Una implementación genérica sin archivos temporales.

#!/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
fuente
Gracias :-) ¡Esto es exactamente lo que estaba buscando!
Qorbani
0

La respuesta de Stéphane es buena, pero preferiría

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

que iterará sobre las teclas de la pidmatriz, independientemente de las entradas que aún existan, para que pueda adaptarlo, salir del ciclo y reiniciar todo el ciclo y simplemente funcionará. Y no necesita valores consecutivos de ipara empezar.

Por supuesto, si está lidiando con miles de procesos, entonces quizás el enfoque de Stépane sería fraccionalmente más eficiente cuando tenga una lista no dispersa.

Martin Kealey
fuente