надійний код повернення фонового процесу


14

Припустимо наступний фрагмент баш-коду:

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

Чи можна припустити, що він $?дійсно містить код повернення, fooа не код повернення ping? Якщо відповідь на це питання: "Ви цього не можете припустити". то як я можу змінити цей фрагмент коду, щоб бути впевненим, що він $?завжди містить код повернення foo?

Відповіді:


12

З bash, у вас буде ця гарантія, якщо ви не розпочали інше фонове завдання (і будьте уважні, що фонові завдання можна починати з, &а також із coprocзаміною та заміщенням процесів) між foo &і wait.

POSIX вимагає, щоб оболонка запам'ятовувала статус виходу принаймні 25 завдань після їх закінчення , але bashпам'ятає набагато більше, ніж це.

Тепер, якщо ви робите:

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

У вас немає гарантії, що barне буде надано такий самий pid, як foo(якщо fooтермін припинився до моменту barпочатку), тому, хоча це малоймовірно, це wait "$pid"може дати вам статус виходу bar.

Ви можете відтворити його за допомогою:

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

який (зрештою) дасть вам 0замість 12.

Щоб уникнути проблеми, одним із способів було б записати її як:

{
  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

Так, ви можете розраховувати на wait "$!"отримання статусу фонового завдання. Під час виконання сценарію, bash не автоматично збирає виконані фонові завдання. Таким чином, якщо ви запускаєтесь wait, вона збиратиме роботу в той час, коли waitназивається.

Ви можете перевірити це за допомогою простого сценарію:

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

Що виведе:

FG: 0
BG: 22

Ключовою частиною цього твердження був початок, "при запуску як сценарій". Коли він інтерактивний, waitне працює. Процес збирається, а стан виходу відкидається безпосередньо перед відображенням запиту (за замовчуванням).
Патрік

Я просто спробував це на bash 4.2.37, 4.1.2 та 3.2.48. Усі вони поводяться абсолютно однаково (буквальна копія / вставка коду у моїй відповіді). wait %1Зазнає невдачі з «немає такої роботи» , як фоновий процес збирається відразу після «сну 5» завершується.
Патрік

Ах гаразд, вибачте, я зараз зрозумів. Я сумував за вашим %1на місці $!.
Стефан Шазелас

Зауважте, що bash -c '(sleep 1;exit 5) & sleep 2; wait %1; echo $?'(так само не інтерактивне) не вдається отримати статус виходу з цієї мертвої роботи. Звучить як клоп.
Стефан Шазелас

це не працювало для мене в рецепті Makefile, поки я не включив його set +e. Здається, що set -eфункція bash вбиває сценарій, як тільки піднімається поганий код виходуwait
user5359531

0

Я вважаю, що ваше припущення є правильним. Ось уривок з man bashпитань очікування фонових процесів.

Якщо n вказує неіснуючий процес або завдання, стан повернення становить 127. В іншому випадку стан повернення - це статус виходу останнього очікуваного процесу або завдання.

Тож, можливо, вам слід перевірити 127

Є подібне запитання з абсолютно іншою відповіддю, ніж це може допомогти.

Сценарій Bash чекає процесів і отримує код повернення

редагувати 1

Натхненний коментарями та відповідями @ Стефана, я розширив його сценарій. Я можу розпочати близько 34 фонових процесів, перш ніж він починає втрачати шлях.

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
$ 

на моїй системі виробляє

$ 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
Ні, дивіться мого коментаря до відповіді Умлауте , і спробуйте саміbash -c '(exit 12) & sleep 1; wait "$!"; echo "$?"'
Стефан Шазелас

Я ніколи не бачив bashвільних доріжок (навіть після початку тисяч робочих місць), моїм прикладом було продемонструвати повторне використання pid, що може бути те, що ви спостерігали і у вашому випадку.
Стефан Шазелас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.