如何运行带有超时的命令,以便在超过超时阈值时将其杀死?
这个答案到自动执行命令行命令-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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(24)
如果您已经知道要在超时(例如
3
秒)后终止的程序的名称(假设program
),我可以提供一个简单但有些肮脏的替代方案解决方案:如果我使用系统调用来调用基准流程,这将非常有效。
If you already know the name of the program (let's assume
program
) to terminate after the timeout (as an example3
seconds), I can contribute a simple and somewhat dirty alternative solution:This works perfectly if I call benchmark processes with system calls.
还有 Martin Cracauer 的
cratimeout
(用 C 语言编写,适用于 Unix 和 Linux 系统)。There's also
cratimeout
by Martin Cracauer (written in C for Unix and Linux systems).OS X 尚未使用 bash 4,也没有 /usr/bin/timeout,因此这里有一个无需 home-brew 或 macports 即可在 OS X 上运行的函数,类似于 /usr/bin/timeout (基于 Tino 的回答)。 参数验证、帮助、用法和对其他信号的支持是读者的练习。
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.
这是一个不依赖于生成子进程的版本 - 我需要一个嵌入此功能的独立脚本。 它还执行分数轮询间隔,因此您可以更快地轮询。 超时本来是首选 - 但我被困在旧服务器上
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
超时命令本身有一个
--foreground
选项。 这使得命令可以与用户交互“当不直接从 shell 提示符运行超时时”。我认为提问者一定已经意识到超时命令的非常明显的解决方案,但因此要求替代解决方案。 当我使用
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".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 usingpopen
, 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.如果您想在脚本中执行此操作,请将其放入其中:
If you want to do it in your script, put this in there:
我遇到了一个保留 shell 上下文并允许超时的问题,唯一的问题是它会在超时时停止脚本执行 - 但它符合我提出的需求:
输出:
当然我假设有名为
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:
with the outputs:
of course I assume there was a dir called
scripts
我的问题可能有点不同:我通过 ssh 在远程计算机上启动命令,并且希望在命令挂起时杀死 shell 和子进程。
我现在使用以下内容:
这样,当超时时,命令返回 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 :
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
基于 @loup 的回答...
如果您想让进程超时并静默终止作业/pid 输出,请运行:
这会将后台进程放入子 shell 中,这样您就看不到作业输出。
Building on @loup's answer...
If you want to timeout a process and silence the kill job/pid output, run:
This puts the backgrounded process into a subshell so you don't see the job output.
一种非常简单的方法:
使用pkill(选项-f),您可以使用参数终止特定命令或指定-n以避免终止旧进程。
A very simplistic way:
with pkill (option -f) you can kill your specific command with arguments or specify -n to avoid kill old process.
在 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.
我有一个调用 php 脚本的 cron 作业,有时它会卡在 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:
您可能正在 coreutils 中寻找
timeout
命令。 由于它是 coreutils 的一部分,因此从技术上来说它是一个 C 解决方案,但它仍然是 coreutils。info timeout
了解更多详细信息。这是一个例子:
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:
我认为这正是您所要求的:
http:// www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3
I think this is precisely what you are asking for:
http://www.bashcookbook.com/bashinfo/source/bash-4.0/examples/scripts/timeout3
无论 bash 监视器模式如何,该解决方案都有效。 您可以使用适当的信号来终止 your_command
观察者在给定的超时后终止 your_command ; 该脚本等待缓慢的任务并终止观察者。 请注意,
wait
不适用于不同 shell 的子进程。示例:
This solution works regardless of bash monitor mode. You can use the proper signal to terminate your_command
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:
要在 1 秒后使
slowcommand
超时:timeout 1 Slowcommand || echo "我失败了,可能是由于超时"
判断命令是否超时或者由于自身原因失败,检查状态码是否为124:
注意,当退出状态为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:
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.就这样:
您可以根据需要更改
SIGINT
和10
;)There you go:
you may change the
SIGINT
and10
as you desire ;)您完全可以使用
bash 4.3
及更高版本来完成此操作:示例:
_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,抱歉。
如果您不需要返回码,这可以变得更简单:
注意:
严格来说,您不需要
; 中的
,但是它使事情与;
)更加一致; }
-案例。set +b
可能也可以保留,但安全总比后悔好。除了
--forground
(可能),您可以实现timeout
支持的所有变体。 不过,--preserve-status
有点困难。 这是留给读者的练习;)这个配方可以在 shell 中“自然地”使用(就像
flock fd
一样自然):但是,如上所述,您不能重新导出环境变量自然会以这种方式进入封闭的外壳。
编辑:
现实世界的例子:超时
__git_ps1
以防它花费太长时间(对于缓慢的SSHFS链接之类的事情):Edit2:错误修复。 我注意到不需要
exit 137
并且同时使_timeout
不可靠。Edit3: git 是一个顽固分子,所以它需要双重技巧才能令人满意地工作。
Edit4:在现实世界的 GIT 示例的第一个
_timeout
中忘记了_
。2023-08-06更新:我找到了一个更好的方法来限制
git
的运行时间,所以上面只是一个例子。我现在使用以下内容:
它的作用:
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。You can do this entirely with
bash 4.3
and above: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:
Notes:
Strictly speaking you do not need the
;
in; )
, however it makes thing more consistent to the; }
-case. And theset +b
probably can be left away, too, but better safe than sorry.Except for
--forground
(probably) you can implement all variantstimeout
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
):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):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.I now use following:
What it does:
setsid -w /bin/bash -c 'SCRIPT' bash "$@"
runsSCRIPT
in a new process groupsleep 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 neededwait -n;
waits for either thesleep
or__git_ps1
to returnp=$(/usr/bin/ps --no-headers -opgrp $$) && [ $$ = ${p:-x} ] &&
is just a safeguard to checksetsid
worked and we are really a process group leader$$
works here correctly, as we are within single quoteskill -9 0
unconditionally kills the entire process groupgit
that may still execute/bin/bash
echo "PGRP mismatch $$ $p" >&2'
is never reachedsetsid
is a fakekill
?) did not work as expectedThe 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.我更喜欢“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.
另请参阅 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
超时可能是第一个尝试的方法。 如果超时,您可能需要通知或执行其他命令。 经过大量的搜索和实验,我想出了这个 bash 脚本:
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:
有点老套,但它确实有效。 如果您有其他前台进程,则不起作用(请帮我解决此问题!)
实际上,我认为您可以反转它,满足您的“奖励”标准:
Kinda hacky, but it works. Doesn't work if you have other foreground processes (please help me fix this!)
Actually, I think you can reverse it, meeting your 'bonus' criteria:
脚本简单,代码清晰。 保存到
/usr/local/bin/run
:运行时间过长的命令超时:
对于完成的命令立即结束:
Simple script with code clarity. Save to
/usr/local/bin/run
:Times out a command that runs too long:
Ends immediately for a command that completes: