【问题标题】:Bash: wait with timeoutBash:等待超时
【发布时间】:2012-04-19 04:56:30
【问题描述】:

在 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 上调用。

有什么想法吗?

【问题讨论】:

  • 你可以sleep 60 代替吗?效率不高,但更简单
  • "60" 必须是最大执行时间上限。应用程序的实际运行时间可能会低很多。所以不,这对我来说效率很低。
  • 如果这些程序真的需要你使用kill -9,它们就坏了。另见iki.fi/era/unix/award.html#kill

标签: linux bash shell


【解决方案1】:

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

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

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

如果是这样,那么保存 timeout PID 也同样简单:

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

【讨论】:

  • 根据gnu.org/software/coreutils/manual/html_node/…-k 应该在60s 之前,此外,您必须为-k 指定超时。因此,例如,第一个代码示例应该是timeout -k 60s 60s app1 &
  • @pepan 谢谢,准时!
  • osx 10.12 上似乎没有超时。
  • @AnneTheAgile 这是一个特定于 Linux 的问题,因此这一点没有实际意义,尤其是因为该问题一开始是围绕 timeout 构建的。但是,在 OSX/MacOS 上通过例如安装 GNU timeout 很容易。 Homebrew.
【解决方案2】:

将 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 和超时写入一个目录并监视该目录。每分钟左右,阅读条目并检查哪些进程仍然存在以及它们是否已超时。

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

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

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

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

【讨论】:

  • 这不起作用。 wait 要求 pid 是当前 shell 的子代。我收到以下错误:“wait.sh: line 2: wait: pid 22603 is not a child of this shell”。
  • 我刚刚注意到我的方法存在一个问题:这只会杀死运行应用程序的 shell。获取进程列表,查找以$pid为父PID的进程;那应该是应用程序。
  • @histumness:你可以这样做或尝试kill -0 $pid检查进程是否仍然存在。
  • I just noticed one problem with my approach...: 你考虑过使用进程组的技术吗?这是获取 PID(146322) 的 PGID 的一种消息方法:ps -fjww -p 146322 | tail -n 1 | awk '{ print $4 }'。 (在我的例子中:输出 145974)然后 PGID 可以与特殊的 kill 模式一起使用来终止组中的所有进程:kill -- -145974
【解决方案3】:

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

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,几乎可以肯定是我们的超时。

【讨论】:

    【解决方案4】:

    我写了一个 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 ...

    【讨论】:

      【解决方案5】:

      为了放入我的 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
      

      Bash 的 sleep 接受小数秒,并且 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 没有超时参数?也许是因为timeoutkill -0waitwait -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 这样的咒语你也有一定的基础:-)

      【讨论】:

      • 这个答案的精确和优雅就像一把外科医生的刀。
      【解决方案6】:
      app1 &
      app2 &
      sleep 60 &
      
      wait -n
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-09-20
        • 1970-01-01
        • 2015-02-02
        • 1970-01-01
        • 1970-01-01
        • 2020-08-15
        • 2020-07-19
        • 2016-09-04
        相关资源
        最近更新 更多