perl - 如何在 Perl 中使用超时读取实现 'tail -f'?

标签 perl timeout

我的问题是 How do I process input immediately instead of waiting for newline 的对立面。我想继续读取不断增长的日志文件,但在文件未增长达指定秒数后停止。

我在 CPAN 上找到了 Sys::AlarmCall,并尝试如下所示,但是当我运行时它没有超时:

perl progress.tracker.pl progress.tracker.pl

我猜这与与“<> ”运算符相关的自动魔术有关。但我不确定如何重写代码。我可以只显式打开一个文件(而不是任意数量的文件),如果没有指定文件,则默认为标准输入 - 我只希望将它与一个文件名一起使用。

(脚本为每行读取生成一个点,每读取 50 行生成一个换行符,每 25 行点输出一个时间戳。我用它来跟踪长时间运行的构建的进度。当前的化身由 tail -f 馈送,但是当这个脚本执行时,这并没有退出,主要是因为它永远不会再获得任何输入来写入现在不存在的进度跟踪器。“最后”行内容是我通常处理的日志文件中的标记;我想删除它。超时将是分钟的数量级,而不是亚秒级。)
#!/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 小时就得到了一个带有我想要添加的花里胡哨的版本。除了调试(我使用的)File::Tail 之外,我不确定我是否能够尽快将它们导入 Perl。一个奇怪的地方:我将代码设置为使用 4096 字节缓冲区;我发现我监控的构建过程中的一行长度超过 5000 字节。嗯 - 代码仍然使用 4096 字节缓冲区,但会为这样的超长行发出一个点。足以满足我的目的。我还发现我需要在构建输出中允许 5 分钟的暂停。

最佳答案

你试过了吗File::Tail处理实际的拖尾而不是试图强制 来完成这项工作?

或者,如果那件作品确实有效,那么失败的原因是什么?

关于perl - 如何在 Perl 中使用超时读取实现 'tail -f'?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/441437/

相关文章:

perl - 如何在制表符上拆分 Perl 中的变量?

mysql - 从perl将阿拉伯语插入mysql

javascript - 使用 Tor 网络执行 JavaScript,无需人工交互

javascript - 几次成功调用后的 Nodejs Node-SOAP ETIMEDOUT

c - FILE* 读取超时

regex - 从已截断为不同长度的字符串末尾删除单词

mysql - 尝试通过使用多个表作为过滤器,在 Perl 中使用 sqlite 从数据库中提取数据

java - 在 Ldap 搜索上设置方法超时

nginx - 增加504超时错误

ruby - 调整 Nokogiri 连接的超时