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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/687948/
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-09-09 18:04:15  来源:igfitidea点击:

Timeout a command in bash without unnecessary delay

bashcommand-linetimeoututilities

提问by system PAUSE

This answerto 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:

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

( /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 tlrbsffor fun.)

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

So this nifty 1-liner approach has a couple of problems. First, the sleepisn'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 tlrbsfcommand finishes in 2 seconds — highly undesirable. Second, the killis unconditional, so this sequence will attempt to kill a non-running process and whine about it.

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

So...

所以...

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

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

  • has a bash implementation (the other question already has Perl and C answers)
  • will terminate at the earlier of the two: tlrbsfprogram termination, or timeout elapsed
  • will not kill non-existing/non-running processes (or, optionally: will not complainabout a bad kill)
  • doesn't have to be a 1-liner
  • can run under Cygwin or Linux
  • 有一个 bash 实现(另一个问题已经有 Perl 和 C 的答案)
  • 将在两者中较早的一个终止:tlrbsf程序终止,或超时已过
  • 不会杀死不存在/未运行的进程(或者,可选:不会抱怨一个坏的杀死)
  • 不一定是 1-liner
  • 可以在 Cygwin 或 Linux 下运行

... and, for bonus points, runs the tlrbsfcommand in the foreground and any 'sleep' or extra process in the background, such that the stdin/stdout/stderr of the tlrbsfcommand can be redirected, same as if it had been run directly?

...并且,为了获得奖励积分,在前台运行tlrbsf命令并在后台运行任何“睡眠”或额外进程,以便可以重定向tlrbsf命令的 stdin/stdout/stderr ,就像它已经被重定向一样直接运行?

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.

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

采纳答案by Juliano

I think this is precisely what you are asking for:

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

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

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 "$@"

回答by yingted

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

您可能正在timeoutcoreutils 中查找命令。由于它是 coreutils 的一部分,所以它在技术上是一个 C 解决方案,但它仍然是 coreutils。info timeout更多细节。下面是一个例子:

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

回答by Dmitry

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

无论 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

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

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

Examples:

例子:

  • your_command runs more than 2 seconds and was terminated
  • your_command 运行超过 2 秒并被终止

your_command interrupted

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 finished before the timeout (20 seconds)
  • your_command 在超时之前完成(20 秒)

your_command finished

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

回答by Aarian P. Aleahmad

There you go:

你去吧:

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

you may change the SIGINTand 10as you desire ;)

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

回答by Tino

You can do this entirely with bash 4.3and above:

您可以完全使用bash 4.3及以上执行此操作:

_timeout() { ( set +b; sleep "" & "${@: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.
  • 例子: _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 wait -n

  • 如果命令被终止,则给出 137,否则给出命令的返回值。
  • 适用于管道。(您无需在此处进入前台!)
  • 也适用于内部 shell 命令或函数。
  • 在子shell中运行,所以没有变量导出到当前shell中,抱歉。

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

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

_timeout() { ( set +b; sleep "" & "${@: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 +bprobably can be left away, too, but better safe than sorry.

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

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

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

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

这个配方可以在外壳中“自然地”使用(与 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_ps1in case it takes too long (for things like slow SSHFS-Links):

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

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

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

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

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

Edit4: Forgot a _in the first _timeoutfor the real world GIT example.

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

回答by maxy

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

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

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

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.

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

回答by pixelbeat

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

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

回答by lance.dolan

To timeout the slowcommandafter 1 second:

slowcommand在 1 秒后超时:

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

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:

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

# ping 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 timeoutcommand, 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.

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

回答by strager

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

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

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

回答by user2099484

timeoutis 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 bashscript:

超时可能是第一种尝试的方法。如果超时,您可能需要通知或其他命令来执行。经过大量的搜索和试验,我想出了这个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