如何运行带有超时的命令,以便在超过超时阈值时将其杀死?

发布于 2024-07-15 07:33:30 字数 1136 浏览 5 评论 0原文

这个答案自动执行命令行命令-kill a command after a certain amount of time

提出了一种 1 行方法来使 bash 命令行中的长时间运行命令超时:

( /path/to/slow command with options ) & sleep 5 ; kill $!

但是给定的“长时间运行”命令可能会早于超时。
(让我们称其为“通常长时间运行但有时很快”命令,或者有趣的是tlrbsf。)

因此,这种漂亮的单行方法有几个问题。
首先,睡眠不是有条件的,因此这为序列完成所需的时间设置了一个不合需要的下限。 当 tlrbsf 命令在 2 秒内完成时,请考虑 30 秒或 2 m 甚至 5 m 的睡眠时间 - 非常不可取。
其次,kill 是无条件的,因此该序列将尝试终止未运行的进程并抱怨它。

那么...

有没有办法让一个典型的长时间运行但有时很快(“tlrbsf”)命令超时,该命令

  • 具有 bash 实现(另一个问题已经有 Perl 和 C 答案)
  • 将在两者中较早的一个终止:tlrbsf程序终止,或者超时
  • 不会杀死不存在/未运行的进程(或者,可选:不会< em>抱怨关于一个糟糕的杀戮)
  • 不一定是 1-liner
  • 可以在 Cygwin 或 Linux 下运行

......并且,为了获得奖励积分,

  • 在前台运行 tlrbsf 命令
  • 后台是否有任何“睡眠”或额外进程

,以便可以重定向 tlrbsf 命令的 stdin/stdout/stderr,就像直接运行它一样?

如果是这样,请分享您的代码。 如果不是,请解释原因。

我花了一段时间尝试破解上述示例,但我已经达到了 bash 技能的极限。

This answer to Command line command to auto-kill a command after a certain amount of time

proposes a 1-line method to timeout a long-running command from the bash command line:

( /path/to/slow command with options ) & sleep 5 ; kill $!

But it's possible that a given "long-running" command may finish earlier than the timeout.
(Let's call it a "typically-long-running-but-sometimes-fast" command, or tlrbsf for fun.)

So this nifty 1-liner approach has a couple of problems.
First, the sleep isn't conditional, so that sets an undesirable lower bound on the time taken for the sequence to finish. Consider 30s or 2m or even 5m for the sleep, when the tlrbsf command finishes in 2 seconds — highly undesirable.
Second, the kill is unconditional, so this sequence will attempt to kill a non-running process and whine about it.

So...

Is there a way to timeout a typically-long-running-but-sometimes-fast ("tlrbsf") command that

  • has a bash implementation (the other question already has Perl and C answers)
  • will terminate at the earlier of the two: tlrbsf program termination, or timeout elapsed
  • will not kill non-existing/non-running processes (or, optionally: will not complain about a bad kill)
  • doesn't have to be a 1-liner
  • can run under Cygwin or Linux

... and, for bonus points

  • runs the tlrbsf command in the foreground
  • any 'sleep' or extra process in the background

such that the stdin/stdout/stderr of the tlrbsf command can be redirected, same as if it had been run directly?

If so, please share your code. If not, please explain why.

I have spent awhile trying to hack the aforementioned example but I'm hitting the limit of my bash skills.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(24

扛刀软妹 2024-07-22 07:33:31

如果您已经知道要在超时(例如 3 秒)后终止的程序的名称(假设 program),我可以提供一个简单但有些肮脏的替代方案解决方案:

(sleep 3 && killall program) & ./program

如果我使用系统调用来调用基准流程,这将非常有效。

If you already know the name of the program (let's assume program) to terminate after the timeout (as an example 3 seconds), I can contribute a simple and somewhat dirty alternative solution:

(sleep 3 && killall program) & ./program

This works perfectly if I call benchmark processes with system calls.

稀香 2024-07-22 07:33:31

还有 Martin Cracauer 的 cratimeout(用 C 语言编写,适用于 Unix 和 Linux 系统)。

# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 1
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'

There's also cratimeout by Martin Cracauer (written in C for Unix and Linux systems).

# cf. http://www.cons.org/cracauer/software.html
# usage: cratimeout timeout_in_msec cmd args
cratimeout 5000 sleep 1
cratimeout 5000 sleep 600
cratimeout 5000 tail -f /dev/null
cratimeout 5000 sh -c 'while sleep 1; do date; done'
寄与心 2024-07-22 07:33:31

OS X 尚未使用 bash 4,也没有 /usr/bin/timeout,因此这里有一个无需 home-brew 或 macports 即可在 OS X 上运行的函数,类似于 /usr/bin/timeout (基于 Tino 的回答)。 参数验证、帮助、用法和对其他信号的支持是读者的练习。

# implement /usr/bin/timeout only if it doesn't exist
[ -n "$(type -p timeout 2>&1)" ] || function timeout { (
    set -m +b
    sleep "$1" &
    SPID=${!}
    ("${@:2}"; RETVAL=$?; kill ${SPID}; exit $RETVAL) &
    CPID=${!}
    wait %1
    SLEEPRETVAL=$?
    if [ $SLEEPRETVAL -eq 0 ] && kill ${CPID} >/dev/null 2>&1 ; then
      RETVAL=124
      # When you need to make sure it dies
      #(sleep 1; kill -9 ${CPID} >/dev/null 2>&1)&
      wait %2
    else
      wait %2
      RETVAL=$?
    fi
    return $RETVAL
) }

OS X doesn't use bash 4 yet, nor does it have /usr/bin/timeout, so here's a function that works on OS X without home-brew or macports that is similar to /usr/bin/timeout (based on Tino's answer). Parameter validation, help, usage, and support for other signals are an exercise for reader.

# implement /usr/bin/timeout only if it doesn't exist
[ -n "$(type -p timeout 2>&1)" ] || function timeout { (
    set -m +b
    sleep "$1" &
    SPID=${!}
    ("${@:2}"; RETVAL=$?; kill ${SPID}; exit $RETVAL) &
    CPID=${!}
    wait %1
    SLEEPRETVAL=$?
    if [ $SLEEPRETVAL -eq 0 ] && kill ${CPID} >/dev/null 2>&1 ; then
      RETVAL=124
      # When you need to make sure it dies
      #(sleep 1; kill -9 ${CPID} >/dev/null 2>&1)&
      wait %2
    else
      wait %2
      RETVAL=$?
    fi
    return $RETVAL
) }
孤独陪着我 2024-07-22 07:33:31

这是一个不依赖于生成子进程的版本 - 我需要一个嵌入此功能的独立脚本。 它还执行分数轮询间隔,因此您可以更快地轮询。 超时本来是首选 - 但我被困在旧服务器上

# wait_on_command <timeout> <poll interval> command
wait_on_command()
{
    local timeout=$1; shift
    local interval=$1; shift
    $* &
    local child=$!

    loops=$(bc <<< "($timeout * (1 / $interval)) + 0.5" | sed 's/\..*//g')
    ((t = loops))
    while ((t > 0)); do
        sleep $interval
        kill -0 $child &>/dev/null || return
        ((t -= 1))
    done

    kill $child &>/dev/null || kill -0 $child &>/dev/null || return
    sleep $interval
    kill -9 $child &>/dev/null
    echo Timed out
}

slow_command()
{
    sleep 2
    echo Completed normally
}

# wait 1 sec in 0.1 sec increments
wait_on_command 1 0.1 slow_command

# or call an external command
wait_on_command 1 0.1 sleep 10

Here is a version that does not rely on spawning a child process - I needed a standalone script which embedded this functionality. It also does a fractional poll interval, so you can poll quicker. timeout would have been preferred - but I'm stuck on an old server

# wait_on_command <timeout> <poll interval> command
wait_on_command()
{
    local timeout=$1; shift
    local interval=$1; shift
    $* &
    local child=$!

    loops=$(bc <<< "($timeout * (1 / $interval)) + 0.5" | sed 's/\..*//g')
    ((t = loops))
    while ((t > 0)); do
        sleep $interval
        kill -0 $child &>/dev/null || return
        ((t -= 1))
    done

    kill $child &>/dev/null || kill -0 $child &>/dev/null || return
    sleep $interval
    kill -9 $child &>/dev/null
    echo Timed out
}

slow_command()
{
    sleep 2
    echo Completed normally
}

# wait 1 sec in 0.1 sec increments
wait_on_command 1 0.1 slow_command

# or call an external command
wait_on_command 1 0.1 sleep 10
口干舌燥 2024-07-22 07:33:31

超时命令本身有一个 --foreground 选项。 这使得命令可以与用户交互“当不直接从 shell 提示符运行超时时”。

timeout --foreground the_command its_options

我认为提问者一定已经意识到超时命令的非常明显的解决方案,但因此要求替代解决方案。 当我使用popen调用它时,timeout对我不起作用,即“不是直接从shell”。 但是,我不要假设这可能是提问者案例中的原因。 查看其手册页

The timeout command itself has a --foreground option. This lets the command interact with the user "when not running timeout directly from a shell prompt".

timeout --foreground the_command its_options

I think the questioner must have been aware of the very obvious solution of the timeout command, but asked for an alternate solution for this reason. timeout did not work for me when I called it using popen, i.e. 'not directly from the shell'. However, let me not assume that this may have been the reason in the questioner's case. Take a look at its man page.

薄凉少年不暖心 2024-07-22 07:33:31

如果您想在脚本中执行此操作,请将其放入其中:

parent=$
( sleep 5 && kill -HUP $parent ) 2>/dev/null &

If you want to do it in your script, put this in there:

parent=$
( sleep 5 && kill -HUP $parent ) 2>/dev/null &
彩扇题诗 2024-07-22 07:33:31

我遇到了一个保留 shell 上下文并允许超时的问题,唯一的问题是它会在超时时停止脚本执行 - 但它符合我提出的需求:

#!/usr/bin/env bash

safe_kill()
{
  ps aux | grep -v grep | grep $1 >/dev/null && kill ${2:-} $1
}

my_timeout()
{
  typeset _my_timeout _waiter_pid _return
  _my_timeout=$1
  echo "Timeout($_my_timeout) running: $*"
  shift
  (
    trap "return 0" USR1
    sleep $_my_timeout
    echo "Timeout($_my_timeout) reached for: $*"
    safe_kill $
  ) &
  _waiter_pid=$!
  "$@" || _return=$?
  safe_kill $_waiter_pid -USR1
  echo "Timeout($_my_timeout) ran: $*"
  return ${_return:-0}
}

my_timeout 3 cd scripts
my_timeout 3 pwd
my_timeout 3 true  && echo true || echo false
my_timeout 3 false && echo true || echo false
my_timeout 3 sleep 10
my_timeout 3 pwd

输出:

Timeout(3) running: 3 cd scripts
Timeout(3) ran: cd scripts
Timeout(3) running: 3 pwd
/home/mpapis/projects/rvm/rvm/scripts
Timeout(3) ran: pwd
Timeout(3) running: 3 true
Timeout(3) ran: true
true
Timeout(3) running: 3 false
Timeout(3) ran: false
false
Timeout(3) running: 3 sleep 10
Timeout(3) reached for: sleep 10
Terminated

当然我假设有名为 scripts 的目录

I was presented with a problem to preserve the shell context and allow timeouts, the only problem with it is it will stop script execution on the timeout - but it's fine with the needs I was presented:

#!/usr/bin/env bash

safe_kill()
{
  ps aux | grep -v grep | grep $1 >/dev/null && kill ${2:-} $1
}

my_timeout()
{
  typeset _my_timeout _waiter_pid _return
  _my_timeout=$1
  echo "Timeout($_my_timeout) running: $*"
  shift
  (
    trap "return 0" USR1
    sleep $_my_timeout
    echo "Timeout($_my_timeout) reached for: $*"
    safe_kill $
  ) &
  _waiter_pid=$!
  "$@" || _return=$?
  safe_kill $_waiter_pid -USR1
  echo "Timeout($_my_timeout) ran: $*"
  return ${_return:-0}
}

my_timeout 3 cd scripts
my_timeout 3 pwd
my_timeout 3 true  && echo true || echo false
my_timeout 3 false && echo true || echo false
my_timeout 3 sleep 10
my_timeout 3 pwd

with the outputs:

Timeout(3) running: 3 cd scripts
Timeout(3) ran: cd scripts
Timeout(3) running: 3 pwd
/home/mpapis/projects/rvm/rvm/scripts
Timeout(3) ran: pwd
Timeout(3) running: 3 true
Timeout(3) ran: true
true
Timeout(3) running: 3 false
Timeout(3) ran: false
false
Timeout(3) running: 3 sleep 10
Timeout(3) reached for: sleep 10
Terminated

of course I assume there was a dir called scripts

新人笑 2024-07-22 07:33:31
#! /bin/bash
timeout=10
interval=1
delay=3
(
    ((t = timeout)) || :

    while ((t > 0)); do
        echo "$t"
        sleep $interval
        # Check if the process still exists.
        kill -0 $ 2> /dev/null || exit 0
        ((t -= interval)) || :
    done

    # Be nice, post SIGTERM first.
    { echo SIGTERM to $ ; kill -s TERM $ ; sleep $delay ; kill -0 $ 2> /dev/null && { echo SIGKILL to $ ; kill -s KILL $ ; } ; }
) &

exec "$@"
#! /bin/bash
timeout=10
interval=1
delay=3
(
    ((t = timeout)) || :

    while ((t > 0)); do
        echo "$t"
        sleep $interval
        # Check if the process still exists.
        kill -0 $ 2> /dev/null || exit 0
        ((t -= interval)) || :
    done

    # Be nice, post SIGTERM first.
    { echo SIGTERM to $ ; kill -s TERM $ ; sleep $delay ; kill -0 $ 2> /dev/null && { echo SIGKILL to $ ; kill -s KILL $ ; } ; }
) &

exec "$@"
攒眉千度 2024-07-22 07:33:31

我的问题可能有点不同:我通过 ssh 在远程计算机上启动命令,并且希望在命令挂起时杀死 shell 和子进程。

我现在使用以下内容:

ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'

这样,当超时时,命令返回 255,或者成功时命令的返回码。

请注意,从 ssh 会话终止进程的处理方式与交互式 shell 不同。 但您也可以使用 ssh 的 -t 选项来分配伪终端,因此它的作用就像交互式 shell

My problem was maybe a bit different : I start a command via ssh on a remote machine and want to kill the shell and childs if the command hangs.

I now use the following :

ssh server '( sleep 60 && kill -9 0 ) 2>/dev/null & my_command; RC=$? ; sleep 1 ; pkill -P $! ; exit $RC'

This way the command returns 255 when there was a timeout or the returncode of the command in case of success

Please note that killing processes from a ssh session is handled different from an interactive shell. But you can also use the -t option to ssh to allocate a pseudo terminal, so it acts like an interactive shell

爱给你人给你 2024-07-22 07:33:31

基于 @loup 的回答...

如果您想让进程超时并静默终止作业/pid 输出,请运行:

( (sleep 1 && killall program 2>/dev/null) &) && program --version 

这会将后台进程放入子 shell 中,这样您就看不到作业输出。

Building on @loup's answer...

If you want to timeout a process and silence the kill job/pid output, run:

( (sleep 1 && killall program 2>/dev/null) &) && program --version 

This puts the backgrounded process into a subshell so you don't see the job output.

蓝戈者 2024-07-22 07:33:31

一种非常简单的方法:

# command & sleep 5; pkill -9 -x -f "command"

使用pkill(选项-f),您可以使用参数终止特定命令或指定-n以避免终止旧进程。

A very simplistic way:

# command & sleep 5; pkill -9 -x -f "command"

with pkill (option -f) you can kill your specific command with arguments or specify -n to avoid kill old process.

失眠症患者 2024-07-22 07:33:31

在 99% 的情况下,答案是不实现任何超时逻辑。 超时逻辑几乎在任何情况下都是一个红色警告信号,表明其他有问题,应该修复

您的进程有时会在 n 秒后挂起或中断吗? 然后找出原因并解决它。

顺便说一句,要正确执行 strager 的解决方案,您需要使用 wait "$SPID" 而不是 fg 1,因为在脚本中您没有作业控制(并且尝试打开它是愚蠢的)。 此外,fg 1 依赖于这样一个事实:您之前没有在脚本中启动任何其他作业,这是一个错误的假设。

In 99% of the cases the answer is NOT to implement any timeout logic. Timeout logic is in nearly any situation a red warning sign that something else is wrong and should be fixed instead.

Is your process hanging or breaking after n seconds sometimes? Then find out why and fix that instead.

As an aside, to do strager's solution right, you need to use wait "$SPID" instead of fg 1, since in scripts you don't have job control (and trying to turn it on is stupid). Moreover, fg 1 relies on the fact that you didn't start any other jobs previously in the script which is a bad assumption to make.

橘味果▽酱 2024-07-22 07:33:31

我有一个调用 php 脚本的 cron 作业,有时它会卡在 php 脚本上。 这个解决方案对我来说非常完美。

我用:

scripttimeout -t 60 /script.php

I have a cron job that calls a php script and, some times, it get stuck on php script. This solution was perfect to me.

I use:

scripttimeout -t 60 /script.php
嘿嘿嘿 2024-07-22 07:33:30

您可能正在 coreutils 中寻找 timeout 命令。 由于它是 coreutils 的一部分,因此从技术上来说它是一个 C 解决方案,但它仍然是 coreutils。 info timeout 了解更多详细信息。
这是一个例子:

timeout 5 /path/to/slow/command with options

You are probably looking for the timeout command in coreutils. Since it's a part of coreutils, it is technically a C solution, but it's still coreutils. info timeout for more details.
Here's an example:

timeout 5 /path/to/slow/command with options
ゝ偶尔ゞ 2024-07-22 07:33:30

我认为这正是您所要求的:

http:// www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3

#!/bin/bash
#
# The Bash shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.

# Hello Chet,
# please find attached a "little easier"  :-)  to comprehend
# time-out example.  If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <[email protected]>

scriptName="${0##*/}"

declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1

# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY

function printUsage() {
    cat <<EOF

Synopsis
    $scriptName [-t timeout] [-i interval] [-d delay] command
    Execute a command with a time-out.
    Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
    signal is blocked, then the subsequent SIGKILL (9) terminates it.

    -t timeout
        Number of seconds to wait for command completion.
        Default value: $DEFAULT_TIMEOUT seconds.

    -i interval
        Interval between checks if the process is still alive.
        Positive integer, default value: $DEFAULT_INTERVAL seconds.

    -d delay
        Delay between posting the SIGTERM signal and destroying the
        process by SIGKILL. Default value: $DEFAULT_DELAY seconds.

As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}

# Options.
while getopts ":t:i:d:" option; do
    case "$option" in
        t) timeout=$OPTARG ;;
        i) interval=$OPTARG ;;
        d) delay=$OPTARG ;;
        *) printUsage; exit 1 ;;
    esac
done
shift $((OPTIND - 1))

# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
    printUsage
    exit 1
fi

# kill -0 pid   Exit code indicates if a signal may be sent to $pid process.
(
    ((t = timeout))

    while ((t > 0)); do
        sleep $interval
        kill -0 $ || exit 0
        ((t -= interval))
    done

    # Be nice, post SIGTERM first.
    # The 'exit 0' below will be executed if any preceeding command fails.
    kill -s SIGTERM $ && kill -0 $ || exit 0
    sleep $delay
    kill -s SIGKILL $
) 2> /dev/null &

exec "$@"

I think this is precisely what you are asking for:

http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3

#!/bin/bash
#
# The Bash shell script executes a command with a time-out.
# Upon time-out expiration SIGTERM (15) is sent to the process. If the signal
# is blocked, then the subsequent SIGKILL (9) terminates it.
#
# Based on the Bash documentation example.

# Hello Chet,
# please find attached a "little easier"  :-)  to comprehend
# time-out example.  If you find it suitable, feel free to include
# anywhere: the very same logic as in the original examples/scripts, a
# little more transparent implementation to my taste.
#
# Dmitry V Golovashkin <[email protected]>

scriptName="${0##*/}"

declare -i DEFAULT_TIMEOUT=9
declare -i DEFAULT_INTERVAL=1
declare -i DEFAULT_DELAY=1

# Timeout.
declare -i timeout=DEFAULT_TIMEOUT
# Interval between checks if the process is still alive.
declare -i interval=DEFAULT_INTERVAL
# Delay between posting the SIGTERM signal and destroying the process by SIGKILL.
declare -i delay=DEFAULT_DELAY

function printUsage() {
    cat <<EOF

Synopsis
    $scriptName [-t timeout] [-i interval] [-d delay] command
    Execute a command with a time-out.
    Upon time-out expiration SIGTERM (15) is sent to the process. If SIGTERM
    signal is blocked, then the subsequent SIGKILL (9) terminates it.

    -t timeout
        Number of seconds to wait for command completion.
        Default value: $DEFAULT_TIMEOUT seconds.

    -i interval
        Interval between checks if the process is still alive.
        Positive integer, default value: $DEFAULT_INTERVAL seconds.

    -d delay
        Delay between posting the SIGTERM signal and destroying the
        process by SIGKILL. Default value: $DEFAULT_DELAY seconds.

As of today, Bash does not support floating point arithmetic (sleep does),
therefore all delay/time values must be integers.
EOF
}

# Options.
while getopts ":t:i:d:" option; do
    case "$option" in
        t) timeout=$OPTARG ;;
        i) interval=$OPTARG ;;
        d) delay=$OPTARG ;;
        *) printUsage; exit 1 ;;
    esac
done
shift $((OPTIND - 1))

# $# should be at least 1 (the command to execute), however it may be strictly
# greater than 1 if the command itself has options.
if (($# == 0 || interval <= 0)); then
    printUsage
    exit 1
fi

# kill -0 pid   Exit code indicates if a signal may be sent to $pid process.
(
    ((t = timeout))

    while ((t > 0)); do
        sleep $interval
        kill -0 $ || exit 0
        ((t -= interval))
    done

    # Be nice, post SIGTERM first.
    # The 'exit 0' below will be executed if any preceeding command fails.
    kill -s SIGTERM $ && kill -0 $ || exit 0
    sleep $delay
    kill -s SIGKILL $
) 2> /dev/null &

exec "$@"
等待圉鍢 2024-07-22 07:33:30

无论 bash 监视器模式如何,该解决方案都有效。 您可以使用适当的信号来终止 your_command

#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

观察者在给定的超时后终止 your_command ; 该脚本等待缓慢的任务并终止观察者。 请注意,wait 不适用于不同 shell 的子进程。

示例:

  • your_command 运行超过 2 秒并被终止

你的命令被中断

( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi
  • your_command 在超时(20 秒)之前完成

your_command 已完成

( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi

This solution works regardless of bash monitor mode. You can use the proper signal to terminate your_command

#!/bin/sh
( your_command ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher

The watcher kills your_command after given timeout; the script waits for the slow task and terminates the watcher. Note that wait does not work with processes which are children of a different shell.

Examples:

  • your_command runs more than 2 seconds and was terminated

your_command interrupted

( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi
  • your_command finished before the timeout (20 seconds)

your_command finished

( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
    echo "your_command finished"
    pkill -HUP -P $watcher
    wait $watcher
else
    echo "your_command interrupted"
fi
听不够的曲调 2024-07-22 07:33:30

要在 1 秒后使 slowcommand 超时:

timeout 1 Slowcommand || echo "我失败了,可能是由于超时"

判断命令是否超时或者由于自身原因失败,检查状态码是否为124:

# ping the address 8.8.8.8 for 3 seconds, but timeout after only 1 second
timeout 1 ping 8.8.8.8 -w3
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 124 ]
then
echo 'Process Timed Out!'
else
echo 'Process did not timeout. Something else went wrong.'
fi
exit $EXIT_STATUS

注意,当退出状态为124时,不知道它是否由于您的 timeout 命令而超时,或者命令本身是否由于其自身的某些内部超时逻辑而终止,然后返回 124。不过,您可以放心地假设,在任何一种情况下,发生某种超时。

To timeout the slowcommand after 1 second:

timeout 1 slowcommand || echo "I failed, perhaps due to time out"

To determine whether the command timed out or failed for its own reasons, check whether the status code is 124:

# ping the address 8.8.8.8 for 3 seconds, but timeout after only 1 second
timeout 1 ping 8.8.8.8 -w3
EXIT_STATUS=$?
if [ $EXIT_STATUS -eq 124 ]
then
echo 'Process Timed Out!'
else
echo 'Process did not timeout. Something else went wrong.'
fi
exit $EXIT_STATUS

Note that when the exit status is 124, you don't know whether it timed out due to your timeout command, or whether the command itself terminated due to some internal timeout logic of its own and then returned 124. You can safely assume in either case, though, that a timeout of some kind happened.

爱要勇敢去追 2024-07-22 07:33:30

就这样:

timeout --signal=SIGINT 10 /path/to/slow command with options

您可以根据需要更改 SIGINT10 ;)

There you go:

timeout --signal=SIGINT 10 /path/to/slow command with options

you may change the SIGINT and 10 as you desire ;)

提赋 2024-07-22 07:33:30

您完全可以使用 bash 4.3 及更高版本来完成此操作:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
  • 示例:_timeout 5 longrunning_command args

  • 示例:{ _timeout 5 生产者 || 回声 KABOOM $?; } | 消费者

  • 示例:生产者 | { _timeout 5 消费者1; 消费者2; }

  • 示例:{ while date; 睡觉.3; 完毕; } | _超时 5 猫 | 少

  • 需要 Bash 4.3 来实现 wait -n

  • 如果命令被终止,则给出 137,否则给出命令的返回值。

  • 适用于管道。 (您不需要在这里进入前台!)

  • 也可以使用内部 shell 命令或函数。

  • 在子 shell 中运行,因此没有变量导出到当前 shell,抱歉。

如果您不需要返回码,这可以变得更简单:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }

注意:

  • 严格来说,您不需要 ; 中的 ; ),但是它使事情与 更加一致; }-案例。 set +b 可能也可以保留,但安全总比后悔好。

  • 除了--forground(可能),您可以实现timeout支持的所有变体。 不过,--preserve-status有点困难。 这是留给读者的练习;)

这个配方可以在 shell 中“自然地”使用(就像 flock fd 一样自然):

(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)

但是,如上所述,您不能重新导出环境变量自然会以这种方式进入封闭的外壳。

编辑:

现实世界的例子:超时__git_ps1以防它花费太长时间(对于缓慢的SSHFS链接之类的事情):

eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }

Edit2:错误修复。 我注意到不需要 exit 137 并且同时使 _timeout 不可靠。

Edit3: git 是一个顽固分子,所以它需要双重技巧才能令人满意地工作。

Edit4:在现实世界的 GIT 示例的第一个 _timeout 中忘记了 _


2023-08-06更新:我​​找到了一个更好的方法来限制git的运行时间,所以上面只是一个例子。

以下内容不再仅适用于 bash,因为它需要 setsid。 但我发现无法仅使用 bash 惯用语来可靠创建流程组领导者,抱歉。

这个方法使用起来有点困难,但是非常有效,因为它不仅杀死了子进程,还杀死了子进程放置在同一进程组中的所有内容。

我现在使用以下内容

__git_ps1() { setsid -w /bin/bash -c 'sleep 1 & . /usr/lib/git-core/git-sh-prompt && __git_ps1 "$@" & wait -n; p=$(/usr/bin/ps --no-headers -opgrp $) && [ $ = ${p:-x} ] && /usr/bin/kill -9 0; echo "PGRP mismatch $ $p" >&2' bash "$@"; }

它的作用:

  • setsid -w /bin/bash - c 'SCRIPT' bash "$@" 在新进程组中运行 SCRIPT
  • sleep 1 & 设置超时
  • 。 /usr/lib/git-core/git-sh-prompt && __git_ps1 "$@" & 并行运行 git 提示符
    • /usr/lib/git-core/git-sh-prompt 适用于 Ubuntu 22.04,如果需要请更改
  • wait -n; 等待 sleep< /code> 或 __git_ps1 返回
    • 第一个获胜

  • p=$(/usr/bin/ps --no-headers -opgrp $$) && [ $$ = ${p:-x} ] && 只是检查 setsid 是否有效的保障措施,我们确实是流程组领导者
    • $$ 在这里可以正常工作,因为我们在单引号内
  • kill -9 0 无条件杀死整个进程组
    • 所有仍可能执行的 git
    • 包括/bin/bash
  • echo "PGRP Mismatch $$ $p" >&2' 永远不会到达
    • 这会通知您 setsid 是假的
    • 或者其他东西(kill?)没有按预期工作

该保护措施可防止 setsid 的情况发生并不像宣传的那样工作。 如果没有,您当前的 shell 可能会被杀死,这将导致无法生成交互式 shell。

如果您使用配方并信任 setsid,您可能不需要保护措施,因此 setsid 是唯一需要的非 bash-idiom。

You can do this entirely with bash 4.3 and above:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
  • Example: _timeout 5 longrunning_command args

  • Example: { _timeout 5 producer || echo KABOOM $?; } | consumer

  • Example: producer | { _timeout 5 consumer1; consumer2; }

  • Example: { while date; do sleep .3; done; } | _timeout 5 cat | less

  • Needs Bash 4.3 for wait -n

  • Gives 137 if the command was killed, else the return value of the command.

  • Works for pipes. (You do not need to go foreground here!)

  • Works with internal shell commands or functions, too.

  • Runs in a subshell, so no variable export into the current shell, sorry.

If you do not need the return code, this can be made even simpler:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; kill -9 `jobs -p`; ) }

Notes:

  • Strictly speaking you do not need the ; in ; ), however it makes thing more consistent to the ; }-case. And the set +b probably can be left away, too, but better safe than sorry.

  • Except for --forground (probably) you can implement all variants timeout supports. --preserve-status is a bit difficult, though. This is left as an exercise for the reader ;)

This recipe can be used "naturally" in the shell (as natural as for flock fd):

(
set +b
sleep 20 &
{
YOUR SHELL CODE HERE
} &
wait -n
kill `jobs -p`
)

However, as explained above, you cannot re-export environment variables into the enclosing shell this way naturally.

Edit:

Real world example: Time out __git_ps1 in case it takes too long (for things like slow SSHFS-Links):

eval "__orig$(declare -f __git_ps1)" && __git_ps1() { ( git() { _timeout 0.3 /usr/bin/git "$@"; }; _timeout 0.3 __orig__git_ps1 "$@"; ) }

Edit2: Bugfix. I noticed that exit 137 is not needed and makes _timeout unreliable at the same time.

Edit3: git is a die-hard, so it needs a double-trick to work satisfyingly.

Edit4: Forgot a _ in the first _timeout for the real world GIT example.


Update 2023-08-06: I found a better way to restrict the runtime of git, so the above is just an example.

The following is no more bash-only as it needs setsid. But I found no way to reliably create process group leaders with just bash idioms, sorry.

This recipe is a bit more difficult to use, but very effective, as it not only kills the child, it also kills everything the child places in the same process group.

I now use following:

__git_ps1() { setsid -w /bin/bash -c 'sleep 1 & . /usr/lib/git-core/git-sh-prompt && __git_ps1 "$@" & wait -n; p=$(/usr/bin/ps --no-headers -opgrp $) && [ $ = ${p:-x} ] && /usr/bin/kill -9 0; echo "PGRP mismatch $ $p" >&2' bash "$@"; }

What it does:

  • setsid -w /bin/bash -c 'SCRIPT' bash "$@" runs SCRIPT in a new process group
  • sleep 1 & sets the timeout
  • . /usr/lib/git-core/git-sh-prompt && __git_ps1 "$@" & runs the git prompt in parallel
    • /usr/lib/git-core/git-sh-prompt is for Ubuntu 22.04, change it if needed
  • wait -n; waits for either the sleep or __git_ps1 to return
    • The first one wins
  • p=$(/usr/bin/ps --no-headers -opgrp $$) && [ $$ = ${p:-x} ] && is just a safeguard to check setsid worked and we are really a process group leader
    • $$ works here correctly, as we are within single quotes
  • kill -9 0 unconditionally kills the entire process group
    • all git that may still execute
    • including the /bin/bash
  • echo "PGRP mismatch $$ $p" >&2' is never reached
    • This informs you that either setsid is a fake
    • or something else (kill?) did not work as expected

The safeguard protects against the case that setsid does not work as advertised. Without your current shell might get killed, which would make it impossible to spawn an interactive shell.

If you use the recipe and trust setsid, you probably do not need the safeguard, so setsid is the only non-bash-idiom this needs.

终遇你 2024-07-22 07:33:30

我更喜欢“timelimit”,它至少在 debian 中有一个包。

http://devel.ringlet.net/sysutils/timelimit/

它比coreutils“超时”,因为它在终止进程时打印一些内容,并且默认情况下它还会在一段时间后发送 SIGKILL 。

I prefer "timelimit", which has a package at least in debian.

http://devel.ringlet.net/sysutils/timelimit/

It is a bit nicer than the coreutils "timeout" because it prints something when killing the process, and it also sends SIGKILL after some time by default.

平生欢 2024-07-22 07:33:30

另请参阅 http://www.pixelbeat.org/scripts/timeout 脚本,其功能已集成到较新的 coreutils 中

See also the http://www.pixelbeat.org/scripts/timeout script the functionality of which has been integrated into newer coreutils

森末i 2024-07-22 07:33:30

超时可能是第一个尝试的方法。 如果超时,您可能需要通知或执行其他命令。 经过大量的搜索和实验,我想出了这个 bash 脚本:

if 
    timeout 20s COMMAND_YOU_WANT_TO_EXECUTE;
    timeout 20s AS_MANY_COMMANDS_AS_YOU_WANT;
then
    echo 'OK'; #if you want a positive response
else
    echo 'Not OK';
    AND_ALTERNATIVE_COMMANDS
fi

timeout is probably the first approach to try. You may need notification or another command to execute if it times out. After quite a bit of searching and experimenting, I came up with this bash script:

if 
    timeout 20s COMMAND_YOU_WANT_TO_EXECUTE;
    timeout 20s AS_MANY_COMMANDS_AS_YOU_WANT;
then
    echo 'OK'; #if you want a positive response
else
    echo 'Not OK';
    AND_ALTERNATIVE_COMMANDS
fi
走过海棠暮 2024-07-22 07:33:30

有点老套,但它确实有效。 如果您有其他前台进程,则不起作用(请帮我解决此问题!)

sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}

实际上,我认为您可以反转它,满足您的“奖励”标准:

(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa

Kinda hacky, but it works. Doesn't work if you have other foreground processes (please help me fix this!)

sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}

Actually, I think you can reverse it, meeting your 'bonus' criteria:

(YOUR COMMAND HERE & SPID=${!}; (sleep TIMEOUT; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}) < asdf > fdsa
执手闯天涯 2024-07-22 07:33:30

脚本简单,代码清晰。 保存到 /usr/local/bin/run

#!/bin/bash

# run
# Run command with timeout $1 seconds.

# Timeout seconds
timeout_seconds="$1"
shift

# PID
pid=$

# Start timeout
(
  sleep "$timeout_seconds"
  echo "Timed out after $timeout_seconds seconds"
  kill -- -$pid &>/dev/null
) &
timeout_pid=$!

# Run
"$@"

# Stop timeout
kill $timeout_pid &>/dev/null

运行时间过长的命令超时:

$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$

对于完成的命令立即结束:

$ run 10 sleep 2
$

Simple script with code clarity. Save to /usr/local/bin/run:

#!/bin/bash

# run
# Run command with timeout $1 seconds.

# Timeout seconds
timeout_seconds="$1"
shift

# PID
pid=$

# Start timeout
(
  sleep "$timeout_seconds"
  echo "Timed out after $timeout_seconds seconds"
  kill -- -$pid &>/dev/null
) &
timeout_pid=$!

# Run
"$@"

# Stop timeout
kill $timeout_pid &>/dev/null

Times out a command that runs too long:

$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$

Ends immediately for a command that completes:

$ run 10 sleep 2
$
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文