Bash:等待超时

use*_*136 45 linux bash shell

在Bash脚本中,我想做类似的事情:

app1 &
pidApp1=$!
app2 &
pidApp2=$1

timeout 60 wait $pidApp1 $pidApp2
kill -9 $pidApp1 $pidApp2
Run Code Online (Sandbox Code Playgroud)

即,在后台启动两个应用程序,并给他们60秒完成他们的工作.然后,如果他们没有在那段时间内完成,就杀了他们.

不幸的是,上面的代码不起作用,因为它timeout是一个可执行文件,而它wait是一个shell命令.我尝试将其更改为:

timeout 60 bash -c wait $pidApp1 $pidApp2
Run Code Online (Sandbox Code Playgroud)

但是这仍然不起作用,因为wait只能在同一个shell中启动的PID上调用.

有任何想法吗?

Adr*_*rth 55

您的示例和接受的答案都过于复杂,为什么您不仅仅使用,timeout因为这正是它的用例?如果在发送初始信号后命令仍在运行(参见),该timeout命令甚至有一个内置选项(-k)SIGKILL在发送初始信号后发送以终止命令(SIGTERM默认情况下man timeout).

如果脚本wait在等待之后不一定需要并恢复控制流程,那只是一个问题

timeout -k 60s 60s app1 &
timeout -k 60s 60s app2 &
# [...]
Run Code Online (Sandbox Code Playgroud)

但是,如果确实如此,那么通过保存timeoutPID来实现这一点非常简单:

pids=()
timeout -k 60s 60s app1 &
pids+=($!)
timeout -k 60s 60s app2 &
pids+=($!)
wait "${pids[@]}"
# [...]
Run Code Online (Sandbox Code Playgroud)

例如

$ cat t.sh
#!/bin/bash

echo "$(date +%H:%M:%S): start"
pids=()
timeout 10 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 1 terminated successfully"' &
pids+=($!)
timeout 2 bash -c 'sleep 5; echo "$(date +%H:%M:%S): job 2 terminated successfully"' &
pids+=($!)
wait "${pids[@]}"
echo "$(date +%H:%M:%S): done waiting. both jobs terminated on their own or via timeout; resuming script"
Run Code Online (Sandbox Code Playgroud)

.

$ ./t.sh
08:59:42: start
08:59:47: job 1 terminated successfully
08:59:47: done waiting. both jobs terminated on their own or via timeout; resuming script
Run Code Online (Sandbox Code Playgroud)

  • @AnneTheAgile这是一个特定于Linux的问题,因此尚无定论,尤其是因为该问题是围绕“超时”开始的。但是,通过[Homebrew](https://brew.sh/)在OSX / MacOS上安装GNU`timeout`很容易。 (3认同)
  • 根据https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html,`-k`应该在`60s`之前,此外,你必须为`-k指定超时`. 因此,例如,第一个代码示例应该是`timeout -k 60s 60s app1 &`。 (2认同)
  • “为什么不只使用超时,因为这正是它的用例?” 因为他们不知道分叉作业需要多长时间,只知道它应该在某个点后 N 秒内退出 (2认同)

Aar*_*lla 21

将PID写入文件并启动以下应用程序:

pidFile=...
( app ; rm $pidFile ; ) &
pid=$!
echo $pid > $pidFile
( sleep 60 ; if [[ -e $pidFile ]]; then killChildrenOf $pid ; fi ; ) &
killerPid=$!

wait $pid
kill $killerPid
Run Code Online (Sandbox Code Playgroud)

这将创建另一个进程,该进程在超时时间内休眠,如果到目前为止尚未完成,则会终止进程.

如果进程更快完成,则删除PID文件并终止杀手进程.

killChildrenOf是一个脚本,它获取所有进程并杀死某个PID的所有子进程.有关实现此功能的不同方法,请参阅此问题的答案:杀死所有子进程的最佳方法

如果您想要走出BASH,可以将PID和超时写入目录并观察该目录.每分钟左右,阅读条目并检查哪些进程仍在,以及它们是否已超时.

编辑如果您想知道该过程是否已成功死亡,您可以使用kill -0 $pid

EDIT2或者您可以尝试进程组.kevinarpe说:要获得PID的PGID(146322):

ps -fjww -p 146322 | tail -n 1 | awk '{ print $4 }'
Run Code Online (Sandbox Code Playgroud)

在我的情况下:145974.然后PGID可以与kill的特殊选项一起使用来终止组中的所有进程: kill -- -145974

  • @histumness:你可以这样做或者尝试`kill -0 $ pid`来检查进程是否仍然存在. (3认同)
  • "我刚注意到我的方法存在一个问题......":您是否考虑过使用流程组的技术?这是获取PID的PGID的一种消息方法(146322):`ps -fjww -p 146322 | 尾巴-n 1 | awk'{print $ 4}'`.(在我的例子中:输出`145974`)然后PGID可以与特殊的kill模式一起使用来终止组中的所有进程:`kill - -145974` (3认同)

Bry*_*sen 5

这是 Aaron Digulla 答案的简化版本,它使用了kill -0Aaron Digulla 在评论中留下的技巧:

app &
pidApp=$!
( sleep 60 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

wait $pidApp
kill -0 $killerPid && kill $killerPid
Run Code Online (Sandbox Code Playgroud)

就我而言,我希望既set -e -x安全又返回状态代码,所以我使用了:

set -e -x
app &
pidApp=$!
( sleep 45 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

wait $pidApp
status=$?
(kill -0 $killerPid && kill $killerPid) || true

exit $status
Run Code Online (Sandbox Code Playgroud)

退出状态 143 表示 SIGTERM,几乎可以肯定来自我们的超时。