可靠的后台进程返回码


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

Answers:


12

有了bash,你将有保证,除非你已经开始另一个后台作业(和提防后台作业可以开始&,但也与coproc对之间,并与进程替换)foo &wait

POSIX要求Shell至少要bash记住25个作业离开后的退出状态,但要记住的更多。

现在,如果您这样做:

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

您无法保证bar不会获得与pid相同的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不起作用。在提示出现之前(默认情况下),将收集过程并丢弃退出状态。
Patrick

我只是在bash 4.2.37、4.1.2和3.2.48上尝试过。它们的行为完全相同(我的答案中代码的文字复制/粘贴)。由于wait %1“睡眠5”完成后立即收集后台进程,因此出现“无此类作业” 的失败。
帕特里克

好的,对不起,我现在明白了。我想念你%1代替了你$!
斯特凡Chazelas

请注意bash -c '(sleep 1;exit 5) & sleep 2; wait %1; echo $?'(也是如此非交互的)无法获取该死任务的退出状态。听起来像个虫子。
斯特凡Chazelas

在我加入Makefile配方之前,这对我不起作用set +e。似乎bash的set -e函数会在引发错误的退出代码后立即杀死该脚本,方法是wait
user5359531

0

我相信您的假设是正确的。这是man bash有关等待后台进程的摘录。

如果n指定不存在的进程或作业,则返回状态为127。否则,返回状态为等待的最后一个进程或作业的退出状态。

所以也许你应该检查127

有一个类似的问题,答案可能完全不同。

Bash脚本等待进程并获取返回码

编辑1

受@Stephane的评论和答案的启发,我扩展了他的脚本。在开始松动之前,我可以启动约34个后台进程。

后退

$ 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
没有,看我到umlaute的答复意见,并与自己试一试bash -c '(exit 12) & sleep 1; wait "$!"; echo "$?"'
斯特凡Chazelas

我从来没有见过bash松散的路线(即使在开始了成千上万的工作之后),我的示例演示了pid被重用,这也可能是您在案例中观察到的。
斯特凡Chazelas
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.