如何从Bash脚本中获取进程ID和退出码?

34

我需要一个Bash脚本,它可以执行以下操作:

  • 启动一个后台进程,将所有输出重定向到文件
  • 将进程的退出代码写入文件
  • 立即返回进程的pid(而不是在进程退出时返回)
  • 脚本必须退出

我可以获取pid但无法获取退出代码:

$ executable >>$log 2>&1 &
pid=`jobs -p`

或者说,我可以获取退出码但无法获取pid:

$ executable >>$log;
# blocked on previous line until process exits
echo $0 >>$log;

我怎样才能同时完成所有这些要求?

1个回答

57

进程ID存储在$!中,无需运行jobs命令。而返回状态则由wait命令返回:

$executable >> $log 2>&1 &
pid=$!
wait $!
echo $?  # return status of $executable

编辑 1

如果我理解评论中附加的要求正确,并且你希望脚本立即返回(而不等待命令完成),那么最初的脚本将无法写入命令的退出状态。但很容易让一个中间人在子进程结束后立即写入退出状态。类似于:

sh -c "$executable"' & echo pid=$! > pidfile; wait $!; echo $? > exit-status' &

应该可以工作。

编辑2

正如评论中指出的那样,该解决方案存在竞争条件:主脚本在pid文件写入之前终止。 OP通过执行轮询睡眠循环来解决这个问题,这是一种可怕的做法,我担心我晚上会有困难入睡,因为我可能激励了这样的灾难。 在我看来,正确的做法是等待子进程完成。 既然这是不可接受的,那么这里有一种解决方法,通过在读取pid文件之前阻塞读取而不是执行循环睡眠:

{ sh -c "$executable > $log 2>&1 &"'
echo $! > pidfile
echo   # Alert parent that the pidfile has been written
wait $!
echo $? > exit-status
' & } | read

2
如果你想在子进程完成之前退出脚本,那么不可能在不引发某种时空异常的情况下编写子进程的退出代码。 - William Pursell
重定向不是原子性的。我觉得监控进程可能会看到一个空的pid文件。它应该重定向到一个临时文件,然后将其移动到pid文件,因为重命名是原子性的。 - user1011471
@user1011471 非常好的观点。就我个人而言,我认为在像这样的shell脚本中尝试以健壮和正确的方式执行任务是很危险的,最好编写一个适当的守护进程,但移动tmp文件似乎是一个合理的解决方案。(除了现在可能存在另一个进程覆盖tmp文件的潜在竞争条件...这又回到了第一个观点) - William Pursell
@WilliamPursell 您可以使用 mktemp 来创建临时文件以提高安全性。我真的在努力学习(而不是争论),所以如果这还不够好,请告诉我。此外,我在哪里可以阅读有关适当守护进程与 shell 脚本之间区别的内容? - user1011471
使用 mktemp 不会提供任何额外的安全措施。如果一个进程写入临时文件,然后另一个进程检查并发现锁不存在,那么第一个进程将临时文件复制到锁文件中,然后第二个进程也会这样做。通过“适当的守护程序”,我指的是直接调用系统调用的进程,因此层次较少。我认为你并没有争论的意思;如果看起来像那样,请原谅。 - William Pursell
显示剩余8条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接