Lo script Bash attende i processi e ottiene il codice di ritorno


13

Sto cercando di creare uno script che avvierà molti comandi in background. Per ogni comando in background ho bisogno di ottenere il codice di ritorno.

Ho provato il seguente 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]}

Il mio problema è durante il ciclo di attesa, se il secondo pid termina prima del primo, non sarò in grado di ottenere il codice di ritorno.

So che posso eseguire wait pid1 pid2, ma con questo comando non riesco a ottenere il codice di ritorno di tutti i comandi.

Qualche idea ?

Risposte:


6

Puoi farlo usando una directory temporanea.

# 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

Il problema è maggiore con il tuo

for i in ${#pid[@]}

Quale è for i in 2.

Dovrebbe piuttosto essere:

for i in 1 2

o

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

wait "$pid" si restituirà il codice di uscita del lavoro con bash(e conchiglie POSIX, ma non zsh), anche se il lavoro fosse già terminata quando waitè stato avviato.


5

Un'implementazione generica senza file temporanei.

#!/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"

Grazie :-) Questo è esattamente quello che stavo cercando!
Qorbani,

0

La risposta di Stéphane è buona, ma preferirei

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

che eseguirà l'iterazione sulle chiavi piddell'array, indipendentemente dalle voci ancora esistenti, in modo da poterlo adattare, uscire dal ciclo e riavviare l'intero ciclo e funzionerà. E non hai bisogno di valori consecutivi iper cominciare.

Ovviamente, se hai a che fare con migliaia di processi, forse l'approccio di Stépane sarebbe frazionalmente più efficiente quando hai un elenco non scarso.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.