Linux Bash:等待超时

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/10028820/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 05:37:39  来源:igfitidea点击:

Bash: wait with timeout

linuxbashshell

提问by user1202136

In a Bash script, I would like to do something like:

在 Bash 脚本中,我想做如下事情:

app1 &
pidApp1=$!
app2 &
pidApp2=

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

I.e., launch two applications in the background, and give them 60 seconds to complete their work. Then, if they don't finish within that interval, kill them.

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

Unfortunately, the above does not work, since timeoutis an executable, while waitis a shell command. I tried changing it to:

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

timeout 60 bash -c wait $pidApp1 $pidApp2

But this still does not work, since waitcan only be called on a PID launched within the same shell.

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

Any ideas?

有任何想法吗?

采纳答案by Aaron Digulla

Write the PIDs to files and start the apps like this:

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

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

wait $pid
kill $killerPid

That would create another process that sleeps for the timeout and kills the process if it hasn't completed so far.

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

If the process completes faster, the PID file is deleted and the killer process is terminated.

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

killChildrenOfis a script that fetches all processes and kills all children of a certain PID. See the answers of this question for different ways to implement this functionality: Best way to kill all child processes

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

If you want to step outside of BASH, you could write PIDs and timeouts into a directory and watch that directory. Every minute or so, read the entries and check which processes are still around and whether they have timed out.

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

EDITIf you want to know whether the process has died successfully, you can use kill -0 $pid

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

EDIT2Or you can try process groups. kevinarpesaid: To get PGID for a PID(146322):

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

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

In my case: 145974. Then PGID can be used with a special option of kill to terminate all processes in a group: kill -- -145974

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

回答by Bryan Larsen

Here's a simplified version of Aaron Digulla's answer, which uses the kill -0trick that Aaron Digulla leaves in a comment:

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

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

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

In my case, I wanted to be both set -e -xsafe and return the status code, so I used:

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

An exit status of 143 indicates SIGTERM, almost certainly from our timeout.

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

回答by Adrian Frühwirth

Both your example and the accepted answer are overly complicated, why do you not onlyuse timeoutsince that is exactlyits use case? The timeoutcommand even has an inbuilt option (-k) to send SIGKILLafter sending the initial signal to terminate the command (SIGTERMby default) if the command is still running after sending the initial signal (see man timeout).

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

If the script doesn't necessarily require to waitand resume control flow after waiting it's simply a matter of

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

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

If it does, however, that's just as easy by saving the timeoutPIDs instead:

但是,如果确实如此,那么通过保存timeoutPID也同样简单:

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

E.g.

例如

$ 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

回答by JonatasTeixeira

I wrote a bash function that will wait until PIDs finished or until timeout, that return non zero if timeout exceeded and print all the PIDs not finisheds.

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

To use this is just wait_timeout $timeout $PID1 $PID2 ...

使用这个只是 wait_timeout $timeout $PID1 $PID2 ...

回答by Andreas Spindler

To put in my 2c, we can boild down Teixeira's solution to:

为了放入我的 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's sleepaccepts fractional seconds, and 0.001s = 1 ms = 1 KHz = plenty of time. However, UNIX has no loopholes when it comes to files and processes. try_waitaccomplishes very little.

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

We have to answer some hard questions to get further.

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

Why has waitno timeout parameter? Maybe because the timeout, kill -0, waitand wait -ncommands can tell the machine more precisely what we want.

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

Why is waitbuiltin to Bash in the first place, so that timeout wait PIDis not working? Maybe only so Bash can implement proper signal handling.

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

Consider:

考虑:

$ 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

Whether in the material world or in machines, as we require some ground on which to run, we require some ground on which to wait too.

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

  • When killfails you hardly know why. Unless you wrote the process, or its manual names the circumstances, there is no way to determine a reasonable timeout value.

  • When you have written the process, you can implement a proper TERM handler or even respond to "Auf Wiedersehen!" send to it through a named pipe. Then you have some ground even for a spell like try_wait:-)

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

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

回答by user1931823

app1 &
app2 &
sleep 60 &

wait -n