54

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

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

timeout 60 wait $pidApp1 $pidApp2
kill -9 $pidApp1 $pidApp2

即,在后台启动两个应用程序,并给它们 60 秒的时间来完成它们的工作。然后,如果他们没有在那个间隔内完成,杀死他们。

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

timeout 60 bash -c wait $pidApp1 $pidApp2

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

有任何想法吗?

4

6 回答 6

65

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

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

timeout -k 60s 60s app1 &
timeout -k 60s 60s app2 &
# [...]

但是,如果确实如此,则只需保存timeoutPID 即可:

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

例如

$ 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"

.

$ ./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
于 2014-03-14T07:49:15.070 回答
25

将 PID 写入文件并像这样启动应用程序:

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

wait $pid
kill $killerPid

这将创建另一个进程,该进程会在超时期间休​​眠并在该进程尚未完成时终止该进程。

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

killChildrenOf是一个脚本,它获取所有进程并杀死某个 PID 的所有子进程。有关实现此功能的不同方法,请参阅此问题的答案:Best way to kill all child processes

如果您想跳出 BASH,您可以将 PID 和超时写入一个目录并监视该目录。每分钟左右,阅读条目并检查哪些进程仍然存在以及它们是否已超时。

编辑如果您想知道进程是否已成功终止,您可以使用kill -0 $pid

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

ps -fjww -p 146322 | tail -n 1 | awk '{ print $4 }'

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

于 2012-04-05T12:52:26.930 回答
6

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

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

wait $pidApp
kill -0 $killerPid && kill $killerPid

就我而言,我想既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

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

于 2014-03-13T16:20:03.133 回答
1

我写了一个 bash 函数,它会等到 PID 完成或超时,如果超过超时,则返回非零并打印所有未完成的 PID。

function wait_timeout {
  local limit=${@:1:1}
  local pids=${@:2}
  local count=0
  while true
  do
    local have_to_wait=false
    for pid in ${pids}; do
      if kill -0 ${pid} &>/dev/null; then
        have_to_wait=true
      else
        pids=`echo ${pids} | sed -e "s/${pid}//g"`
      fi
    done
    if ${have_to_wait} && (( $count < $limit )); then
      count=$(( count + 1 ))
      sleep 1
    else
      echo ${pids}
      return 1
    fi
  done   
  return 0
}

使用它只是wait_timeout $timeout $PID1 $PID2 ...

于 2016-12-12T19:21:15.843 回答
1

放入我的 2c,我们可以将 Teixeira 的解决方案归结为:

try_wait() {
    # Usage: [PID]...
    for ((i = 0; i < $#; i += 1)); do
        kill -0 $@ && sleep 0.001 || return 0
    done
    return 1 # timeout or no PIDs
} &>/dev/null

Bashsleep接受小数秒,并且 0.001s = 1 ms = 1 KHz = 充足的时间。但是,UNIX 在文件和进程方面没有漏洞。try_wait收效甚微。

$ cat &
[1] 16574
$ try_wait %1 && echo 'exited' || echo 'timeout'
timeout
$ kill %1
$ try_wait %1 && echo 'exited' || echo 'timeout'
exited

我们必须回答一些棘手的问题才能走得更远。

为什么wait没有超时参数?也许是因为timeout,kill -0和命令可以waitwait -n准确地告诉机器我们想要什么。

为什么首先wait内置到 Bash 中,所以这timeout wait PID不起作用?也许只有这样 Bash 才能实现正确的信号处理。

考虑:

$ timeout 30s cat &
[1] 6680
$ jobs
[1]+    Running   timeout 30s cat &
$ kill -0 %1 && echo 'running'
running
$ # now meditate a bit and then...
$ kill -0 %1 && echo 'running' || echo 'vanished'
bash: kill: (NNN) - No such process
vanished

无论是在物质世界还是在机器中,因为我们需要一些地面来运行,我们也需要一些地面来等待。

  • kill失败时,你几乎不知道为什么。除非你写了进程,或者它的手册名称的情况,没有办法确定一个合理的超时值。

  • 编写完流程后,您可以实现适当的 TERM 处理程序,甚至可以响应“Auf Wiedersehen!”。通过命名管道发送给它。然后,即使是这样的咒语,您也有一定的基础try_wait:-)

于 2018-11-15T22:44:50.107 回答
0
app1 &
app2 &
sleep 60 &

wait -n
于 2019-06-26T15:11:05.720 回答