最好是使用timeout
具有以下用途的命令:
timeout 86400 cmd
当前的(8.23)GNU实现至少alarm()
在等待子进程时使用或等效方法。它似乎并不能防止SIGALRM
在waitpid()
返回和timeout
退出之间进行传递(有效地取消该警报)。在那个小窗口中,timeout
甚至可能在stderr上写消息(例如,如果孩子抛弃了一个内核),这将进一步扩大该竞赛窗口(例如,如果stderr是一个完整的管道,则是无限期的)。
我个人可以忍受这种限制(可能会在将来的版本中修复)。timeout
与您可能会手工做的一样,我们也会格外小心地报告正确的退出状态,处理其他极端情况(例如在启动时阻止/忽略SIGALRM,处理其他信号...)。
作为近似值,您可以这样写perl
:
perl -MPOSIX -e '
$p = fork();
die "fork: $!\n" unless defined($p);
if ($p) {
$SIG{ALRM} = sub {
kill "TERM", $p;
exit 124;
};
alarm(86400);
wait;
exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
} else {exec @ARGV}' cmd
http://devel.ringlet.net/sysutils/timelimit/上有一个timelimit
命令(比GNU早了几个月)。timeout
timelimit -t 86400 cmd
那个使用alarm()
类似的机制,但是在上面安装了一个处理程序SIGCHLD
(忽略停止的孩子)来检测孩子的死亡。它还可以在运行之前取消警报waitpid()
(这不会取消SIGALRM
是否挂起警报,但是不会以书面形式发出警报,我看不出这是一个问题),并在调用之前将其杀死waitpid()
(因此无法杀死重用的pid) )。
netpipes也有一个timelimit
命令。那要比其他所有方法都早几十年,它采用了另一种方法,但是对于停止的命令不能正常工作,并且1
在超时时返回退出状态。
作为对问题的更直接答案,您可以执行以下操作:
if [ "$(ps -o ppid= -p "$p")" -eq "$$" ]; then
kill "$p"
fi
也就是说,请确保该过程仍然是我们的子过程。再次,有一个小的竞争窗口(介于ps
检索该进程的状态和kill
杀死该进程之间),在此期间该进程可能死亡并且其pid被其他进程重用。
对于某些贝壳(zsh
,bash
,mksh
),你可以通过工作规范,而不是的PID。
cmd &
sleep 86400
kill %
wait "$!" # to retrieve the exit status
仅当您仅生成一个后台作业时,这种方法才有效(否则始终无法可靠地获得正确的jobspec)。
如果这是一个问题,只需启动一个新的shell实例:
bash -c '"$@" & sleep 86400; kill %; wait "$!"' sh cmd
之所以行得通,是因为外壳在孩子死亡时从作业表中删除了作业。在这里,不应有任何竞争窗口,因为到shell调用时kill()
,要么尚未处理SIGCHLD信号并且pid无法重用(因为尚未等待pid),要么已经被处理并且作业已从过程表中删除(并且kill
将报告错误)。bash
的kill
至少街区SIGCHLD它访问它的工作表扩大之前%
后和疏导它kill()
。
为了避免具有另一种选择sleep
过程即使挂在cmd
已经死亡,bash
或者ksh93
是使用管道用read -t
的,而不是sleep
:
{
{
cmd 4>&1 >&3 3>&- &
printf '%d\n.' "$!"
} | {
read p
read -t 86400 || kill "$p"
}
} 3>&1
那仍然有竞争条件,您将丢失命令的退出状态。它还假定cmd
不会关闭其fd 4。
您可以尝试实施无竞争解决方案,perl
例如:
perl -MPOSIX -e '
$p = fork();
die "fork: $!\n" unless defined($p);
if ($p) {
$SIG{CHLD} = sub {
$ss = POSIX::SigSet->new(SIGALRM); $oss = POSIX::SigSet->new;
sigprocmask(SIG_BLOCK, $ss, $oss);
waitpid($p,WNOHANG);
exit (WIFSIGNALED($?) ? WTERMSIG($?)+128 : WEXITSTATUS($?))
unless $? == -1;
sigprocmask(SIG_UNBLOCK, $oss);
};
$SIG{ALRM} = sub {
kill "TERM", $p;
exit 124;
};
alarm(86400);
pause while 1;
} else {exec @ARGV}' cmd args...
(尽管需要改进以处理其他类型的转角箱)。
另一种不受种族歧视的方法可能是使用过程组:
set -m
((sleep 86400; kill 0) & exec cmd)
但是请注意,如果涉及到终端设备的I / O,则使用过程组可能会有副作用。尽管可以杀死由产生的所有其他额外进程,但它还有其他好处cmd
。