我想在一段时间后自动终止命令。我想到了这样的界面:
% constrain 300 ./foo args
哪个会用“args”运行“./foo”,但如果它在5分钟后仍在运行,则自动将其杀死。
将这个想法概括为其他约束可能是有用的,例如,如果一个进程使用太多内存,则自动填充进程。
是否有任何现有工具可以做到这一点,或者是否有人写过这样的东西?
ADDED:Jonathan的解决方案正是我的想法,它的工作方式就像是linux上的魅力,但我不能让它在Mac OSX上运行。我摆脱了SIGRTMIN,它让它编译得很好,但信号不会被发送到子进程。任何人都知道如何在Mac上完成这项工作?
[已添加:请注意,Jonathan提供的更新适用于Mac和其他地方。]
答案 0 :(得分:45)
GNU Coreutils包含 timeout 命令,默认安装在许多系统上。
https://www.gnu.org/software/coreutils/manual/html_node/timeout-invocation.html
要观看free -m
一分钟,然后通过发送一个TERM信号来杀死它:
timeout 1m watch free -m
答案 1 :(得分:30)
也许我不理解这个问题,但这听起来很直接,至少在bash:
( /path/to/slow command with options ) & sleep 5 ; kill $!
这将在括号内运行第一个命令五秒钟,然后将其杀死。整个操作同步运行,即在忙于等待慢速命令时,您将无法使用shell。如果这不是你想要的,应该可以添加另一个&。
$!
变量是Bash内置函数,其中包含最近启动的子shell的进程ID。重要的是没有&在括号内,以这种方式丢失进程ID。
答案 2 :(得分:27)
我来这个派对的时间比较晚,但我没有看到答案中列出我最喜欢的技巧。
在* NIX下,alarm(2)
在execve(2)
内继承,SIGALRM默认为致命。所以,你通常可以简单地说:
$ doalarm () { perl -e 'alarm shift; exec @ARGV' "$@"; } # define a helper function
$ doalarm 300 ./foo.sh args
或安装trivial C wrapper为您执行此操作。
优点只涉及一个PID,机制很简单。例如,如果./foo.sh
退出“太快”并重新使用其PID,则不会杀死错误的进程。您不需要多个shell子进程协同工作,这可以正确完成,但更容易出现竞争。
缺点时间受限的流程无法操纵其闹钟(例如alarm(2)
,ualarm(2)
,setitimer(2)
),因为这可能会清除继承的报警。显然,它也不能阻止或忽略SIGALRM,尽管对于其他一些方法也可以说SIGINT,SIGTERM等。
有些(我认为很老,我认为)系统在sleep(2)
方面实现alarm(2)
,即使在今天,一些程序员使用alarm(2)
作为I / O的粗略内部超时机制和其他操作。但是,根据我的经验,这种技术适用于您想要限时的绝大多数流程。
答案 3 :(得分:12)
我有一个名为timeout
的程序,用C语言编写,最初是在1989年,但从那时起定期更新。
<小时/> 更新:此代码无法在MacOS X上编译,因为未定义SIGRTMIN,并且在MacOS X上运行时无法超时,因为
signal()
函数在警报超时后恢复wait()
- 这不是所需的行为。我有timeout.c
的新版本,可以处理这两个问题(使用sigaction()
代替signal()
)。和以前一样,请联系我获取10K gzipped tar文件,其中包含源代码和手册页(参见我的个人资料)。
/*
@(#)File: $RCSfile: timeout.c,v $
@(#)Version: $Revision: 4.6 $
@(#)Last changed: $Date: 2007/03/01 22:23:02 $
@(#)Purpose: Run command with timeout monitor
@(#)Author: J Leffler
@(#)Copyright: (C) JLSS 1989,1997,2003,2005-07
*/
#define _POSIX_SOURCE /* Enable kill() in <unistd.h> on Solaris 7 */
#define _XOPEN_SOURCE 500
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "stderr.h"
#define CHILD 0
#define FORKFAIL -1
static const char usestr[] = "[-vV] -t time [-s signal] cmd [arg ...]";
#ifndef lint
/* Prevent over-aggressive optimizers from eliminating ID string */
const char jlss_id_timeout_c[] = "@(#)$Id: timeout.c,v 4.6 2007/03/01 22:23:02 jleffler Exp $";
#endif /* lint */
static void catcher(int signum)
{
return;
}
int main(int argc, char **argv)
{
pid_t pid;
int tm_out;
int kill_signal;
pid_t corpse;
int status;
int opt;
int vflag = 0;
err_setarg0(argv[0]);
opterr = 0;
tm_out = 0;
kill_signal = SIGTERM;
while ((opt = getopt(argc, argv, "vVt:s:")) != -1)
{
switch(opt)
{
case 'V':
err_version("TIMEOUT", &"@(#)$Revision: 4.6 $ ($Date: 2007/03/01 22:23:02 $)"[4]);
break;
case 's':
kill_signal = atoi(optarg);
if (kill_signal <= 0 || kill_signal >= SIGRTMIN)
err_error("signal number must be between 1 and %d\n", SIGRTMIN - 1);
break;
case 't':
tm_out = atoi(optarg);
if (tm_out <= 0)
err_error("time must be greater than zero (%s)\n", optarg);
break;
case 'v':
vflag = 1;
break;
default:
err_usage(usestr);
break;
}
}
if (optind >= argc || tm_out == 0)
err_usage(usestr);
if ((pid = fork()) == FORKFAIL)
err_syserr("failed to fork\n");
else if (pid == CHILD)
{
execvp(argv[optind], &argv[optind]);
err_syserr("failed to exec command %s\n", argv[optind]);
}
/* Must be parent -- wait for child to die */
if (vflag)
err_remark("time %d, signal %d, child PID %u\n", tm_out, kill_signal, (unsigned)pid);
signal(SIGALRM, catcher);
alarm((unsigned int)tm_out);
while ((corpse = wait(&status)) != pid && errno != ECHILD)
{
if (errno == EINTR)
{
/* Timed out -- kill child */
if (vflag)
err_remark("timed out - send signal %d to process %d\n", (int)kill_signal, (int)pid);
if (kill(pid, kill_signal) != 0)
err_syserr("sending signal %d to PID %d - ", kill_signal, pid);
corpse = wait(&status);
break;
}
}
alarm(0);
if (vflag)
{
if (corpse == (pid_t) -1)
err_syserr("no valid PID from waiting - ");
else
err_remark("child PID %u status 0x%04X\n", (unsigned)corpse, (unsigned)status);
}
if (corpse != pid)
status = 2; /* Dunno what happened! */
else if (WIFEXITED(status))
status = WEXITSTATUS(status);
else if (WIFSIGNALED(status))
status = WTERMSIG(status);
else
status = 2; /* Dunno what happened! */
return(status);
}
如果你想要'stderr.h'和'stderr.c'的'官方'代码,请联系我(见我的个人资料)。
答案 4 :(得分:11)
还有ulimit,可用于限制子进程可用的执行时间。
ulimit -t 10
将进程限制为10秒的CPU时间。
要实际使用它来限制新进程而不是当前进程,您可能希望使用包装器脚本:
#! /usr/bin/env python
import os
os.system("ulimit -t 10; other-command-here")
other-command可以是任何工具。我正在运行不同排序算法的Java,Python,C和Scheme版本,并记录它们花了多长时间,同时将执行时间限制为30秒。 Cocoa-Python应用程序生成了各种命令行 - 包括参数 - 并将时间整理成CSV文件,但它实际上只是在上面提供的命令之上。
答案 5 :(得分:4)
Perl one liner,仅用于踢球:
perl -e '$s = shift; $SIG{ALRM} = sub { print STDERR "Timeout!\n"; kill INT => $p }; exec(@ARGV) unless $p = fork; alarm $s; waitpid $p, 0' 10 yes foo
这会打印'foo'十秒钟,然后超时。将'10'替换为任意秒数,将'yes foo'替换为任何命令。
答案 6 :(得分:4)
答案 7 :(得分:4)
我在perl one-liner上的变化为你提供了退出状态,而没有使用fork()和wait(),而且没有杀死错误进程的风险:
#!/bin/sh
# Usage: timelimit.sh secs cmd [ arg ... ]
exec perl -MPOSIX -e '$SIG{ALRM} = sub { print "timeout: @ARGV\n"; kill(SIGTERM, -$$); }; alarm shift; $exit = system @ARGV; exit(WIFEXITED($exit) ? WEXITSTATUS($exit) : WTERMSIG($exit));' "$@"
基本上fork()和wait()隐藏在system()中。 SIGALRM被传递给父进程,然后通过将SIGTERM发送到整个进程组( - $$)来杀死自己及其子进程。如果孩子退出并且孩子的pid在kill()发生之前被重用,则不会杀死错误的进程,因为带有旧子进程的pid的新进程将不在父进程的同一进程组中
作为一个额外的好处,该脚本还会退出可能正确的退出状态。
答案 8 :(得分:2)
尝试类似:
# This function is called with a timeout (in seconds) and a pid.
# After the timeout expires, if the process still exists, it attempts
# to kill it.
function timeout() {
sleep $1
# kill -0 tests whether the process exists
if kill -0 $2 > /dev/null 2>&1 ; then
echo "killing process $2"
kill $2 > /dev/null 2>&1
else
echo "process $2 already completed"
fi
}
<your command> &
cpid=$!
timeout 3 $cpid
wait $cpid > /dev/null 2>&
exit $?
它的缺点是,如果你的进程'pid在超时内被重用,它可能会杀死错误的进程。这种可能性极小,但您可能每秒启动20000多个进程。这可以修复。
答案 9 :(得分:2)
我使用“timelimit”,这是debian存储库中可用的包。
答案 10 :(得分:2)
#!/bin/sh
( some_slow_task ) & pid=$!
( sleep $TIMEOUT && kill -HUP $pid ) 2>/dev/null & watcher=$!
wait $pid 2>/dev/null && pkill -HUP -P $watcher
观察者在超时后杀死慢速任务;脚本等待缓慢的任务并终止观察者。
缓慢的任务中断
( sleep 20 ) & pid=$!
( sleep 2 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "Slow task finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "Slow task interrupted"
fi
任务完成缓慢
( sleep 2 ) & pid=$!
( sleep 20 && kill -HUP $pid ) 2>/dev/null & watcher=$!
if wait $pid 2>/dev/null; then
echo "Slow task finished"
pkill -HUP -P $watcher
wait $watcher
else
echo "Slow task interrupted"
fi
答案 11 :(得分:1)
稍微修改perl one-liner将使退出状态正确。
perl -e '$s = shift; $SIG{ALRM} = sub { print STDERR "Timeout!\n"; kill INT => $p; exit 77 }; exec(@ARGV) unless $p = fork; alarm $s; waitpid $p, 0; exit ($? >> 8)' 10 yes foo
基本上,退出($?&gt;&gt; 8)将转发子流程的退出状态。我只是在退出状态下选择了77来暂停。
答案 12 :(得分:1)
如何使用expect工具?
## run a command, aborting if timeout exceeded, e.g. timed-run 20 CMD ARGS ...
timed-run() {
# timeout in seconds
local tmout="$1"
shift
env CMD_TIMEOUT="$tmout" expect -f - "$@" <<"EOF"
# expect script follows
eval spawn -noecho $argv
set timeout $env(CMD_TIMEOUT)
expect {
timeout {
send_error "error: operation timed out\n"
exit 1
}
eof
}
EOF
}
答案 13 :(得分:1)
纯粹的bash:
#!/bin/bash
if [[ $# < 2 ]]; then
echo "Usage: $0 timeout cmd [options]"
exit 1
fi
TIMEOUT="$1"
shift
BOSSPID=$$
(
sleep $TIMEOUT
kill -9 -$BOSSPID
)&
TIMERPID=$!
trap "kill -9 $TIMERPID" EXIT
eval "$@"
答案 14 :(得分:1)
有没有办法用“at”设置特定时间来执行此操作?
$ at 05:00 PM kill -9 $pid
似乎更简单。
如果您不知道pid编号是什么,我假设有一种方法可以使用ps aux和grep脚本读取它,但不知道如何实现它。
$ | grep someprogram
tony 11585 0.0 0.0 3116 720 pts/1 S+ 11:39 0:00 grep someprogram
tony 22532 0.0 0.9 27344 14136 ? S Aug25 1:23 someprogram
您的脚本必须读取pid并为其分配变量。 我不是太熟练,但我认为这是可行的。