[英]How do I implement 'tail -f' with timeout-on-read in Perl?
我的问题是如何立即处理输入而不是等待换行的对立面。 我想继续阅读不断增长的日志文件,但在文件没有增长指定的秒数后停止。
我在CPAN上找到了Sys :: AlarmCall ,并尝试如下所示,但是当我运行时它没有超时:
perl progress.tracker.pl progress.tracker.pl
我猜这与使用' <>
'运算符的自动魔法有关。 但我不确定如何重写代码。 我没有明确打开一个文件(而不是任意数量的文件),如果没有指定文件则默认为标准输入 - 我只希望将它与一个文件名一起使用。
(该脚本为每行读取生成一个点,每读取50行生成一个换行符,每25行输出一个时间戳。我用它来跟踪长时间运行的构建的进度。当前的化身由tail -f
,但是当这个脚本没有退出时,主要是因为它永远不会再有任何输入写入现在不存在的进度跟踪器。'last'行东西是我正常处理的日志文件中的标记;我想要删除它。超时将是分钟的顺序,而不是亚秒。)
#!/usr/perl/v5.10.0/bin/perl -w
#
# @(#)$Id: progress.tracker.pl,v 1.3 2009/01/09 17:32:45 jleffler Exp jleffler $
#
# Track progress of a log-generating process by printing one dot per line read.
use strict;
use constant DOTS_PER_LINE => 50;
use constant LINES_PER_BREAK => 25;
use constant debug => 0;
use POSIX qw( strftime );
use Sys::AlarmCall;
sub read_line
{
print "-->> read_line()\n" if debug;
my $line = <STDIN>;
printf "<<-- read_line(): %s", (defined $line) ? $line : "\n" if debug;
return $line;
}
my $line_no = 0;
my $timeout = 30;
my $line;
$| = 1; # Unbuffered output
while ($line = alarm_call($timeout, 'read_line', undef))
{
$line_no++;
print ".";
print "\n" if ($line_no % DOTS_PER_LINE == 0);
printf "%s\n", strftime("%Y-%m-%d %H:%M:%S", localtime(time))
if ($line_no % (DOTS_PER_LINE * LINES_PER_BREAK) == 0);
last if $line =~ m/^Trace run finished: /;
}
print "\n";
print $line if defined $line && $line =~ m/^Trace run finished: /;
有什么建议么? (最好除了'离开你的****并用C代码'!)
File :: Tail似乎很好地满足了我的要求。 修订后的代码是:
#!/usr/perl/v5.10.0/bin/perl -w
#
# @(#)$Id: progress.tracker.pl,v 3.2 2009/01/14 07:17:04 jleffler Exp $
#
# Track progress of a log-generating process by printing one dot per line read.
use strict;
use POSIX qw( strftime );
use File::Tail;
use constant DOTS_PER_LINE => 50;
use constant LINES_PER_BREAK => 25;
use constant MAX_TIMEOUTS => 10;
use constant TIMEOUT_LENGTH => 30; # Seconds
my $timeout = TIMEOUT_LENGTH;
my $line_no = 0;
my $n_timeouts = 0;
my $line;
sub print_item
{
my($item) = @_;
$line_no++;
print "$item";
print "\n" if ($line_no % DOTS_PER_LINE == 0);
printf "%s\n", strftime("%Y-%m-%d %H:%M:%S", localtime(time))
if ($line_no % (DOTS_PER_LINE * LINES_PER_BREAK) == 0);
}
$| = 1; # Unbuffered output
# The foreach and while loops are cribbed from File::Tail POD.
my @files;
foreach my $file (@ARGV)
{
push(@files, File::Tail->new(name=>"$file", tail => -1, interval => 2));
}
while (1)
{
my ($nfound, $timeleft, @pending) = File::Tail::select(undef, undef, undef, $timeout, @files);
unless ($nfound)
{
# timeout - do something else here, if you need to
last if ++$n_timeouts > MAX_TIMEOUTS;
print_item "@";
}
else
{
$n_timeouts = 0; # New data arriving - reset timeouts
foreach my $tail (@pending)
{
# Read one line of the file
$line = $tail->read;
print_item ".";
}
}
}
print "\n";
print $line if defined $line && $line =~ m/^Trace run finished: /;
还有改进的余地; 特别是,超时应该是可配置的。 但是,它似乎按我的意愿工作。 需要更多的实验和调整。
似乎$ tail-> read()函数一次读取一行; POD并不完全明显。
遗憾的是,经过进一步的实际使用,似乎我使用File :: Tail代码的方式不能满足我的需要。 特别是,一旦它停在一个文件上,它似乎不会再次恢复。 我没有花时间试图找出问题所在,而是重新选择了替代方案 - 我自己在C中编写代码。花了不到2个小时的时间来获得一个我想要添加的铃声和口哨的版本。 我不确定我是否能够快速地将这些内容导入Perl,除了(我使用的)File :: Tail的调试之外。 一个奇怪的是:我设置我的代码使用4096字节缓冲区; 我发现构建过程中的一行我监视的长度超过5000字节。 好吧 - 代码仍然使用4096字节的缓冲区,但是为这样的超长线发出一个点。 足够我的目的。 我还发现我需要在构建输出中允许5分钟的暂停。
您是否尝试过File :: Tail来处理实际拖尾而不是强迫<STDIN>来完成这项工作?
或者,如果那件作品确实有效,那么失败的方式是什么?
问题很可能与输出缓冲有关。 如果您想要彻底解释,请阅读:
http://www.pixelbeat.org/programming/stdio_buffering/
在我的情况下(在RHEL上,我希望tail -n 0 -f file | grep -m 1 pattern
在生长文件中出现模式时立即终止),建议的LD_PRELOADED库没有帮助,也没有明确使用unbuffer来自Expect包的实用程序。
但基于博客文章( http://www.smop.co.uk/blog/index.php/2006/06/26/tail-f-and-awk/ ),我发现从尾部重定向输入在子shell中做了诀窍:
grep -m 1 pattern <(tail -n 0 -f file)
但这并不是那么简单。 在交互式shell中工作时,使用SSH远程运行时,同样的命令仍然像往常一样冻结:
ssh login@hostname 'grep -m 1 pattern <(tail -n 0 -f file)'
我发现在这种情况下,必须使用Expect中的unbuffer实用程序解除尾部的输出:
ssh login@hostname 'grep -m 1 pattern <(unbuffer -p tail -n 0 -f file)'
这不能在交互式shell上使用 - unbuffer会导致ioctl(raw): I/O error
!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.