330

这个对命令行命令的回答在一定时间后自动终止命令

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

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

但是给定的“长时间运行”命令可能会在超时之前完成。
(让我们称其为“通常长时间运行但有时很快”的命令,或tlrbsf来表示乐趣。)

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

所以...

有没有办法让一个通常长时间运行但有时快速(“tlrbsf”)命令超时

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

...并且,对于奖励积分

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

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

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

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

4

24 回答 24

614

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

timeout 5 /path/to/slow/command with options
于 2011-01-03T03:04:41.683 回答
163

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

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 "$@"
于 2009-03-26T23:57:56.073 回答
40

无论 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 秒并被终止

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
  • 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
于 2012-06-15T18:29:48.373 回答
26

slowcommand在 1 秒后超时:

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

判断命令是否因自身原因超时或失败,查看状态码是否为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。您可以放心地假设在任何一种情况下,但是,发生了某种超时。

于 2018-10-24T17:46:02.603 回答
25

你去:

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

您可以根据需要更改SIGINTand 10;)

于 2016-03-06T17:34:47.197 回答
20

你可以完全做到这一点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 producer || echo KABOOM $?; } | consumer
  • 例子:producer | { _timeout 5 consumer1; consumer2; }
  • 例子:{ while date; do sleep .3; done; } | _timeout 5 cat | less

  • 需要 Bash 4.3wait -n

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

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

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

笔记:

  • 严格来说,您不需要;in ; ),但是它使事情与; }-case 更加一致。并且set +b可能也可以忽略,但安全总比后悔好。

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

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

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

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

编辑:

现实世界的例子:超时__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 "$@"; ) }

编辑2:错误修复。我注意到这exit 137是不需要_timeout的,同时也变得不可靠。

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

Edit4:_第一次忘记了_timeout真实世界的 GIT 示例。

于 2015-03-02T07:00:41.057 回答
17

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

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

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

于 2010-08-15T10:05:52.960 回答
9

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

于 2010-02-02T12:42:28.650 回答
9

timeout可能是第一种尝试的方法。如果超时,您可能需要通知或执行其他命令。经过相当多的搜索和试验,我想出了这个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
于 2015-04-18T13:34:52.837 回答
8

有点hacky,但它有效。如果您有其他前台进程则不起作用(请帮我解决这个问题!)

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
于 2009-03-26T23:55:33.037 回答
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 2 sleep 10
Timed out after 2 seconds
Terminated
$

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

$ run 10 sleep 2
$
于 2017-06-28T20:44:54.193 回答
4

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

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

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

于 2012-02-01T13:56:10.127 回答
2

还有cratimeoutMartin Cracauer(用 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'
于 2013-03-12T11:58:28.940 回答
2

OS X 还没有使用 bash 4,也没有 /usr/bin/timeout,所以这里有一个在 OS X 上工作的函数,没有 home-brew 或 macports,类似于 /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
) }
于 2015-06-30T19:33:12.663 回答
1

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

# 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
于 2017-04-06T15:35:01.597 回答
0
#! /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 "$@"
于 2013-01-03T20:56:17.030 回答
0

我遇到了一个保留 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

于 2013-01-11T02:40:54.413 回答
0

我的问题可能有点不同:我在远程机器上通过 ssh 启动命令,如果命令挂起,我想杀死 shell 和子进程。

我现在使用以下内容:

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

这样命令在超时时返回 255 或在成功的情况下返回命令的返回码

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

于 2015-07-16T18:48:43.983 回答
0

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

timeout --foreground the_command its_options

我认为提问者一定已经知道该timeout命令的非常明显的解决方案,但出于这个原因要求提供替代解决方案。timeout当我使用它调用它时对我不起作用popen,即'不是直接来自外壳'。但是,让我不要假设这可能是提问者案件的原因。看看它的手册页

于 2020-07-15T16:23:30.090 回答
0

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

parent=$$
( sleep 5 && kill -HUP $parent ) 2>/dev/null &
于 2021-10-13T20:48:08.030 回答
-1

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

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

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

于 2009-03-27T07:34:16.087 回答
-1

@loup 的回答为基础...

如果要使进程超时并使 kill 作业/pid 输出静音,请运行:

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

这会将后台进程放入子外壳中,因此您看不到作业输出。

于 2018-08-30T23:04:44.863 回答
-2

一个非常简单的方法:

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

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

于 2015-02-16T10:29:18.847 回答
-5

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

我用:

scripttimeout -t 60 /script.php
于 2018-10-03T15:50:04.683 回答