zuverlässiger Rückkehrcode des Hintergrundprozesses

12

Nehmen wir den folgenden Teil des Bash-Codes an:

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

Ist es sicher anzunehmen, dass $?tatsächlich der Rückgabecode von foound nicht der Rückgabecode von enthalten ist ping? Wenn die Antwort auf diese Frage lautet: "Das können Sie nicht annehmen." Wie kann ich dann diesen Code ändern, um sicherzustellen, dass er $?immer den Rückkehrcode von enthält foo?

Christopher Schmidt
quelle

Antworten:

12

Mit bash, werden Sie diese Garantie haben , wenn Sie einen anderen Hintergrund - Job begonnen haben (die Hintergrundjobs und passen sie mit gestartet werden , &sondern auch mit coprocund mit Prozess - Substitution) zwischen der foo &und der wait.

POSIX erfordert, dass sich eine Shell den Exit-Status von mindestens 25 Jobsbash merkt, nachdem diese nicht mehr vorhanden sind , sich aber noch viel mehr merkt.

Nun, wenn Sie dies tun:

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

Sie haben keine Garantie, dass Sie barnicht die gleiche PID erhalten wie foo(wenn foosie zu barBeginn der Zeit beendet wurde ). Auch wenn dies unwahrscheinlich ist, wait "$pid"können Sie den Beendigungsstatus von erhalten bar.

Sie können es reproduzieren mit:

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

was wird (irgendwann) geben Sie 0statt 12.

Um das Problem zu vermeiden, wäre eine Möglichkeit, es wie folgt zu schreiben:

{
  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 "$?")
Stéphane Chazelas
quelle
4

Ja, Sie können sich darauf verlassen wait "$!", dass Sie den Status eines Hintergrundjobs erhalten. Wenn bash als Skript ausgeführt wird, werden abgeschlossene Hintergrundjobs nicht automatisch erfasst. Wenn Sie also ausführen wait, wird der Job zum Zeitpunkt des waitAufrufs erfasst.

Sie können dies mit einem einfachen Skript testen:

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

Welches wird ausgegeben:

FG: 0
BG: 22
Patrick
quelle
Der wichtigste Teil dieser Anweisung war der Anfang, "wenn sie als Skript ausgeführt wird". Wenn interaktiv, waitfunktioniert nicht. Der Prozess wird erfasst und der Beendigungsstatus verworfen, kurz bevor die Eingabeaufforderung angezeigt wird (Standardeinstellung).
Patrick
Ich habe es gerade mit Bash 4.2.37, 4.1.2 und 3.2.48 ausprobiert. Alle verhalten sich genau gleich (wörtliches Kopieren / Einfügen des Codes in meine Antwort). Das wait %1schlägt mit "no such job" fehl, da der Hintergrundprozess unmittelbar nach Abschluss von "sleep 5" erfasst wird.
Patrick
Ah ok, sorry, ich verstehe es jetzt. Ich hatte deine %1anstelle von vermisst $!.
Stéphane Chazelas
Beachten Sie, dass bash -c '(sleep 1;exit 5) & sleep 2; wait %1; echo $?'(auch nicht interaktiv) der Beendigungsstatus dieses toten Jobs nicht abgerufen werden kann. Klingt nach einem Bug.
Stéphane Chazelas
0

Ich glaube, Ihre Annahme ist richtig. Hier ist ein Auszug aus dem man bashWarten auf Hintergrundprozesse.

Wenn n einen nicht vorhandenen Prozess oder Job angibt, ist der Rückgabestatus 127. Andernfalls ist der Rückgabestatus der Beendigungsstatus des letzten Prozesses oder Jobs, auf den gewartet wurde.

Vielleicht solltest du nach 127 Ausschau halten

Es gibt eine ähnliche Frage mit einer völlig anderen Antwort, als sie helfen könnte.

Bash-Skript wartet auf Prozesse und ruft den Rückkehrcode ab

bearbeiten 1

Inspiriert von @ Stephanes Kommentaren und Antworten habe ich sein Skript erweitert. Ich kann ungefähr 34 Hintergrundprozesse starten, bevor es anfängt, den Überblick zu verlieren.

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
$ 

auf meinem system produziert

$ 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
X Tian
quelle
1
Nein, sehen Sie sich meinen Kommentar zu Umlautes Antwort an und versuchen Sie es selbst mitbash -c '(exit 12) & sleep 1; wait "$!"; echo "$?"'
Stéphane Chazelas
Ich habe noch nie einen bashlosen Weg gesehen (selbst nach dem Start von Tausenden von Jobs). Mein Beispiel zeigte, dass die PID wiederverwendet wird, was Sie vielleicht auch in Ihrem Fall beobachtet haben.
Stéphane Chazelas