关于linux:重击:等待超时

Bash: wait with timeout

在Bash脚本中,我想执行以下操作:

1
2
3
4
5
6
7
app1 &
pidApp1=$!
app2 &
pidApp2=$1

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

也就是说,在后台启动两个应用程序,并给它们60秒以完成其工作。 然后,如果他们没有在该时间间隔内完成,请杀死他们。

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

1
timeout 60 bash -c wait $pidApp1 $pidApp2

但这仍然行不通,因为wait只能在同一外壳程序内启动的PID上调用。

有任何想法吗?


您的示例和公认的答案都过于复杂,为什么您不仅仅使用timeout,因为这恰恰是它的用例?如果发送初始信号后命令仍在运行,则timeout命令甚至具有内置选项(-k)在发送初始信号后发送SIGKILL以终止命令(默认情况下为SIGTERM)(请参见< x10>)。

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

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

但是,如果确实如此,那么只需保存timeout PID,就可以轻松实现:

1
2
3
4
5
6
7
pids=()
timeout -k 60s 60s app1 &
pids+=($!)
timeout -k 60s 60s app2 &
pids+=($!)
wait"${pids[@]}"
# [...]

例如。

1
2
3
4
5
6
7
8
9
10
11
$ 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"

1
2
3
4
$ ./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


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

1
2
3
4
5
6
7
8
9
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的所有子代。有关实现此功能的不同方法,请参见此问题的答案:杀死所有子进程的最佳方法

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

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

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

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

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


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

1
2
3
4
5
6
7
app &
pidApp=$!
( sleep 60 ; echo 'timeout'; kill $pidApp ) &
killerPid=$!

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

就我而言,我想同时保持set -e -x的安全并返回状态代码,所以我使用了:

1
2
3
4
5
6
7
8
9
10
11
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,几乎可以肯定是我们超时了。


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 ...


1
2
3
4
5
app1 &
app2 &
sleep 60 &

wait -n

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

1
2
3
4
5
6
7
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完成得很少。

1
2
3
4
5
6
7
$ 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才能实施适当的信号处理。

考虑:

1
2
3
4
5
6
7
8
9
10
$ 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 :-)之类的咒语,您也有一定的基础