在bash中超时命令没有不必要的延迟

sys*_*USE 267 bash command-line utilities timeout

这个答案了一定的时间后,命令行命令来自动杀死命令

提出了一个单行方法来从bash命令行超时长时间运行的命令:

( /path/to/slow command with options ) & sleep 5 ; kill $!
Run Code Online (Sandbox Code Playgroud)

但是,给定的"长时间运行"命令可能比超时更早完成.(我们称之为"通常长时间运行但有时快速"的命令,或者称为tlrbsf.)

所以这种漂亮的1-liner方法有几个问题.首先,sleep它不是有条件的,因此在序列完成所花费的时间上设置了不希望的下限.当tlrbsf命令在2秒内完成时,考虑30s或2m甚至5m的睡眠- 非常不受欢迎.其次,它kill是无条件的,所以这个序列将试图杀死一个非运行的进程并发出呜呜声.

所以...

有没有办法超时通常长时间运行但有时快("tlrbsf")命令

  • 有一个bash实现(另一个问题已经有Perl和C的答案)
  • 将在两个之前终止:tlrbsf程序终止或超时
  • 不会杀死不存在/未运行的进程(或者,可选:不会抱怨糟糕的杀戮)
  • 不必是1班轮
  • 可以在Cygwin或Linux下运行

...对于奖励积分,在前台运行tlrbsf命令,在后台运行任何"睡眠"或额外进程,这样tlrbsf命令的stdin/stdout/stderr 可以被重定向,就像它曾经被重定向一样直接跑吗?

如果是这样,请分享您的代码.如果没有,请解释原因.

我花了一段时间试图破解前面提到的例子,但我正在达到我的bash技能的极限.

yin*_*ted 499

您可能正在寻找timeoutcoreutils中的命令.因为它是coreutils的一部分,所以它在技术上是一个C解决方案,但它仍然是coreutils.info timeout更多细节.这是一个例子:

timeout 5 /path/to/slow/command with options
Run Code Online (Sandbox Code Playgroud)

  • 在OS X上通过自制程序安装时,命令变为```gtimeout``` (22认同)
  • 在Mac中,您可以通过Macports或自制软件安装. (20认同)
  • 为了澄清,你在OSX/mac上需要的自制命令是`brew install coreutils`,然后你可以使用`gtimeout`. (7认同)
  • ...你使用的操作系统有哪些具有来自2003年之前的coreutils? (5认同)
  • @Keith:CentOS 5.10,例如:-( (5认同)
  • 这与[`run-one`](https://apps.ubuntu.com/cat/applications/run-one/)和[`curl`](http://stackoverflow.com/questions/14722556)相结合/ using-curl-to-send-email)用于报告. (3认同)
  • 此处的 5 表示 5 秒(默认)。5 分钟,命令将是:“timeout 5m /path/to/slow/command with options” 5 小时,命令将是“timeout 5h /path/to/slow/command with options” (3认同)
  • @Zero`timeout`在超时时退出124. (2认同)

Jul*_*ano 139

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

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 <Dmitry.Golovashkin@sas.com>

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 "$@"
Run Code Online (Sandbox Code Playgroud)

  • 您不必选择轮询间隔,它的默认值为1秒,这非常好.检查非常便宜,开销可以忽略不计.我怀疑这会使tlrbsf明显运行得更长.我用睡眠30测试,并且在使用和不使用它之间得到了0.000ms的差异. (7认同)
  • 对,我现在看到了.如果设置轮询间隔==超时,它符合我的确切要求.也可以在管道中工作,与整个事物一起工作,适用于多个实例和其他正在运行的作业.甜蜜,谢谢! (6认同)
  • @Juliano 这是处理超时的好方法,非常有用。我想知道是否有办法让脚本在超时后终止进程时返回退出代码 143?我尝试在 kill 命令后立即添加“exit 143”,但我总是在调用者脚本中得到退出代码 0。 (2认同)

小智 34

无论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
Run Code Online (Sandbox Code Playgroud)

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

例子:

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

your_command被打断了

( 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
Run Code Online (Sandbox Code Playgroud)
  • 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
Run Code Online (Sandbox Code Playgroud)

  • `wait` 返回它等待的进程的退出状态。因此,如果您的命令确实在分配的时间内退出,但退出状态非零,那么这里的逻辑将表现为超时,即打印“your_command 中断”。相反,你_可以_在没有`if`的情况下执行`wait`,然后检查`$watcher` pid是否仍然存在,如果存在,那么你就知道你没有超时。 (2认同)

Aar*_*mad 21

你去:

timeout --signal=SIGINT 10 /path/to/slow command with options
Run Code Online (Sandbox Code Playgroud)

您可以更改SIGINT10你的愿望;)

  • "timeout"是(至少)Redhat,Centos,Suse和Ubuntu上的**coreutils**包的一部分,所以如果你没有它,你需要安装它. (3认同)

max*_*axy 17

我更喜欢"timelimit",它至少在debian中有一个包.

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

它比coreutils"timeout"好一点,因为它在杀死进程时会输出一些内容,并且默认情况下它会在一段时间后发送SIGKILL.

  • 使用-t2而不是-T2.大-T是从发送SIGTERM到发送SIGKILL的时间. (3认同)

Tin*_*ino 17

你完全可以用以下方法完成bash 4.3:

_timeout() { ( set +b; sleep "$1" & "${@:2}" & wait -n; r=$?; kill -9 `jobs -p`; exit $r; ) }
Run Code Online (Sandbox Code Playgroud)
  • 例: _timeout 5 longrunning_command args
  • 例: { _timeout 5 producer || echo KABOOM $?; } | consumer
  • 例: producer | { _timeout 5 consumer1; consumer2; }
  • 例: { while date; do sleep .3; done; } | _timeout 5 cat | less

  • 需要Bash 4.3 for wait -n

  • 如果命令被杀死,则给出137,否则返回命令的返回值.
  • 用于管道.(你不需要在这里前景!)
  • 也可以使用内部shell命令或函数.
  • 在子shell中运行,所以没有变量导出到当前shell,抱歉.

如果您不需要返回代码,则可以更简单:

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

笔记:

  • 严格来说,你不需要;in ; ),但是它使得; }-case 更加一致.而set +b大概可以离开了,太,但有备无患.

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

这个配方可以在shell中"自然地"使用(如同自然一样flock fd):

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

但是,如上所述,您无法以这种方式将环境变量重新导出到封闭shell中.

编辑:

真实世界的例子:__git_ps1如果花费太长时间超时(对于慢速SSHFS-Links这样的事情):

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

Edit2:修正.我注意到这exit 137不是必需的,同时也不_timeout可靠.

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

编辑4:忘记了_第一个_timeout真实世界的GIT示例.

  • 这实际上需要 Bash 4.3 或更新版本。`cc。'wait' 内置有一个新的 '-n' 选项来等待下一个孩子改变状态。` 来自:http://tiswww.case.edu/php/chet/bash/NEWS (2认同)

pix*_*eat 9

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


str*_*ger 8

有点hacky,但它的工作原理.如果你有其他前台进程不起作用(请帮我解决这个问题!)

sleep TIMEOUT & SPID=${!}; (YOUR COMMAND HERE; kill ${SPID}) & CPID=${!}; fg 1; kill ${CPID}
Run Code Online (Sandbox Code Playgroud)

实际上,我认为你可以扭转它,达到你的"奖金"标准:

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


小智 8

超时可能是第一种尝试的方法.如果超时,您可能需要通知或其他命令才能执行.经过相当多的搜索和实验,我想出了这个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
Run Code Online (Sandbox Code Playgroud)


lan*_*lan 6

slowcommand在1秒后超时:

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

请注意“也许”一词的重要性。通过这种简单的解决方案,错误可能来自于slowcommand自身而并非来自timeout。这是为了进行快速的开发测试,不应在生产管理中使用。


Lyc*_*can 5

具有代码清晰性的简单脚本。保存到/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 Code Online (Sandbox Code Playgroud)

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

$ run 2 sleep 10
Timed out after 2 seconds
Terminated
$
Run Code Online (Sandbox Code Playgroud)

立即结束,以完成以下命令:

$ run 10 sleep 2
$
Run Code Online (Sandbox Code Playgroud)