надежный код возврата фонового процесса


13

Давайте предположим следующий фрагмент кода 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

Можно ли предположить, что $?действительно содержит код возврата, 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

Вдохновленный комментариями и ответами @ Stephane, я расширил его сценарий. Я могу запустить около 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.