codice di ritorno affidabile del processo in background


13

Supponiamo il seguente pezzo di codice bash:

foo > logfile 2>&1 &
foo_pid=$!

while ps -p$foo_pid
do
    ping -c 1 localhost
done

wait $foo_pid

if [[ $? == 0 ]]
then
    echo "foo success"
fi

È sicuro supporre che $?contenga effettivamente il codice di ritorno fooe non il codice di ritorno di ping? Se la risposta a questa domanda è: "Non puoi assumerlo." allora come posso modificare questo pezzo di codice per essere sicuro che $?contenga sempre il codice di ritorno di foo?

Risposte:


12

Con bash, avrai quella garanzia a meno che tu non abbia iniziato un altro lavoro in background (e fai attenzione che i lavori in background possono essere avviati &ma anche con coproce con la sostituzione del processo) tra il foo &e il wait.

POSIX richiede che una shell ricordi lo stato di uscita di almeno 25 lavori dopo che sono andati , ma bashricorda molto di più.

Ora, se lo fai:

foo & pid=$!
...
bar &
wait "$pid"

Non hai alcuna garanzia che barnon ti verrà dato lo stesso pid di foo(se fooè terminato prima dell'inizio del tempo bar), quindi anche se è improbabile, wait "$pid"potresti darti lo stato di uscita di bar.

Puoi riprodurlo con:

bash -c '(exit 12; foo) & pid=$!
         while : bar & [ "$pid" != "$!" ]; do :;done
         wait "$pid"; echo "$?"'

che (eventualmente) ti darà 0invece di 12.

Per evitare il problema, un modo sarebbe quello di scriverlo come:

{
  foo_pid=$!

  while ps -p "$foo_pid"
  do
      ping -c 1 localhost
  done

  bar &
  ...

  read <&3 ret
  if [ "$ret" = 0 ]; then
    echo foo was sucessful.
  fi
} 3< <(foo > logfile 2>&1; echo "$?")

4

Sì, puoi fare affidamento wait "$!"per ottenere lo stato di un processo in background. Quando viene eseguito come script, bash non raccoglie automaticamente i lavori in background completati. Pertanto, se si esegue wait, raccoglierà il lavoro nel momento in cui waitviene chiamato.

Puoi testarlo con un semplice script:

#!/bin/bash
sh -c 'sleep 1; exit 22' &
sleep 5
echo "FG: $?"
wait %1
echo "BG: $?"

Che produrrà:

FG: 0
BG: 22

La parte chiave di tale affermazione è stata l'inizio, "quando eseguito come script". Quando interattivo, waitnon funziona. Il processo viene raccolto e lo stato di uscita scartato subito prima della visualizzazione del prompt (per impostazione predefinita).
Patrick,

L'ho appena provato su bash 4.2.37, 4.1.2 e 3.2.48. Tutti si comportano esattamente allo stesso modo (letterale copia / incolla del codice nella mia risposta). L' wait %1errore ha esito negativo con "nessun processo" poiché il processo in background viene raccolto immediatamente dopo il completamento di "sleep 5".
Patrick,

Ah ok, scusa, ho capito adesso. Mi ero perso %1al posto di $!.
Stéphane Chazelas,

Si noti che bash -c '(sleep 1;exit 5) & sleep 2; wait %1; echo $?'(quindi anche non interattivo) non riesce a ottenere lo stato di uscita di quel lavoro morto. Sembra un bug.
Stéphane Chazelas,

questo non ha funzionato per me all'interno di una ricetta Makefile fino a quando non l'ho incluso set +e. Sembra che la set -efunzione di bash uccida lo script non appena viene sollevato un codice di uscita wait
errato

0

Credo che la tua ipotesi sia corretta. Ecco un estratto dall'attesa man bashdei processi in background.

Se n specifica un processo o processo inesistente, lo stato di restituzione è 127. In caso contrario, lo stato di restituzione è lo stato di uscita dell'ultimo processo o processo atteso.

Quindi forse dovresti controllare 127

C'è una domanda simile con una risposta completamente diversa da quella che potrebbe aiutare.

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

modifica 1

Ispirato dai commenti e dalle risposte di @ Stephane, ho ampliato la sua sceneggiatura. Posso iniziare circa 34 processi in background prima che inizi a perdere traccia.

tback

$ cat tback 
plist=()
elist=()
slist=([1]=12 [2]=15 [3]=17 [4]=19 [5]=21 [6]=23)
count=30

#start background tasksto monitor
for i in 1 2 3 4
do
  #echo pid $i ${plist[$i]} ${slist[$i]}
  (echo $BASHPID-${slist[$i]} running; exit ${slist[$i]}) & 
  plist[$i]=$!
done

echo starting $count background echos to test history
for i in `eval echo {1..$count}`
do
  echo -n "." &
  elist[$i]=$! 
done
# wait for each background echo to complete
for i in `eval echo {1..$count}`
do
  wait ${elist[$i]}
  echo -n $? 
done
echo ""
# Now wait for each monitored process and check return status with expected
failed=0
for i in 1 2 3 4
do
  wait ${plist[$i]}
  rv=$?
  echo " pid ${plist[$i]} returns $rv should be ${slist[$i]}"
  if [[ $rv != ${slist[$i]} ]] 
  then
    failed=1
  fi
done

wait
echo "Complete $failed"
if [[ $failed = "1" ]]
then
  echo Failed
else
  echo Success
fi
exit $failed
$ 

sul mio sistema produce

$ bash tback
14553-12 running
14554-15 running
14555-17 running
starting 30 background echos to test history
14556-19 running
..............................000000000000000000000000000000
 pid 14553 returns 12 should be 12
 pid 14554 returns 15 should be 15
 pid 14555 returns 17 should be 17
 pid 14556 returns 19 should be 19
Complete 0
Success

1
No, vedi il mio commento alla risposta di umlaute e provalo tu stesso conbash -c '(exit 12) & sleep 1; wait "$!"; echo "$?"'
Stéphane Chazelas,

Non ho mai visto bashuna traccia libera (anche dopo aver avviato migliaia di lavori), il mio esempio stava dimostrando il riutilizzo del pid, che potrebbe essere anche quello che hai osservato nel tuo caso.
Stéphane Chazelas,
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.