【问题标题】:How do I implement 'tail -f' with timeout-on-read in Perl?如何在 Perl 中实现带有超时读取的“tail -f”?
【发布时间】:2014-05-08 10:49:32
【问题描述】:

我的问题是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 分钟的暂停。

【问题讨论】:

    标签: perl timeout


    【解决方案1】:

    您是否尝试过 File::Tail 来处理实际拖尾问题,而不是试图强迫 完成这项工作?

    或者,如果这件作品确实有效,那么这会以什么方式失败?

    【讨论】:

    • 否;我还没有尝试过。但我会……我想我应该知道的。我对超时的搜索没有找到。
    • 对于像/dev/ttyUSB0这样的非文件设备你是怎么做的?我尝试了 File::Tail,但它在该设备上不起作用,而 tail -f 工作正常。
    • 不能在标准输入上使用 File::Tail
    【解决方案2】:

    问题很可能与输出缓冲有关。如果您想获得详尽的解释,请阅读:

    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 中启动的 tail 重定向输入可以解决问题:

    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!

    【讨论】:

      猜你喜欢
      • 2023-03-25
      • 1970-01-01
      • 1970-01-01
      • 2010-12-14
      • 1970-01-01
      • 1970-01-01
      • 2020-04-30
      • 1970-01-01
      相关资源
      最近更新 更多