简体   繁体   English

如何在Perl中使用timeout-on-read实现'tail -f'?

[英]How do I implement 'tail -f' with timeout-on-read in Perl?

My question is the antithesis of How do I process input immediately instead of waiting for newline . 我的问题是如何立即处理输入而不是等待换行的对立面。 I want to continue reading a growing log file, but stop after the file has not grown for a specified number of seconds. 我想继续阅读不断增长的日志文件,但在文件没有增长指定的秒数后停止。

I found Sys::AlarmCall at CPAN, and tried as shown below, but it doesn't time-out when I run: 我在CPAN上找到了Sys :: AlarmCall ,并尝试如下所示,但是当我运行时它没有超时:

perl progress.tracker.pl progress.tracker.pl

I'm guessing that this is something to do with the auto-magic associated with the ' <> ' operator. 我猜这与使用' <> '运算符的自动魔法有关。 But I'm not sure how to go about rewriting the code. 但我不确定如何重写代码。 I'm OK with an explicit open of just one file (instead of an arbitrary number of files), defaulting to standard input if no file is specified - I only ever expect to use it with one file name. 我没有明确打开一个文件(而不是任意数量的文件),如果没有指定文件则默认为标准输入 - 我只希望将它与一个文件名一起使用。

(The script generates a dot for each line read, generating a newline every 50 lines read, and outputting a timestamp every 25 lines of dots. I use it to track the progress of long-running builds. The current incarnation is fed by tail -f , but that is not exiting when this script does, mainly because it never gets any more input to write to the now non-existent progress tracker. The 'last' line stuff is a marker in the log files I normally process; I want to remove it. The timeout will be of the order of minutes, not sub-second.) (该脚本为每行读取生成一个点,每读取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: /;

Any suggestions? 有什么建议么? (Preferably apart from 'get off your **** and code it in C'!) (最好除了'离开你的****并用C代码'!)


File::Tail seems to meet my requirements pretty well. File :: Tail似乎很好地满足了我的要求。 The revised code is: 修订后的代码是:

#!/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: /;

There is room for improvement; 还有改进的余地; in particular, the timeouts should be configurable. 特别是,超时应该是可配置的。 However, it seems to work as I wanted. 但是,它似乎按我的意愿工作。 More experimentation and tweaking is required. 需要更多的实验和调整。

It seems that the $tail->read() function reads one line at a time; 似乎$ tail-> read()函数一次读取一行; that is not totally obvious from the POD. POD并不完全明显。


Sadly, upon further practical use, it appears that the way I'm using the File::Tail code doesn't work the way I need it to. 遗憾的是,经过进一步的实际使用,似乎我使用File :: Tail代码的方式不能满足我的需要。 In particular, once it stalls on a file, it doesn't seem to resume again. 特别是,一旦它停在一个文件上,它似乎不会再次恢复。 Rather than spend the time trying to work out what was wrong, I fell back on the alternative - code it myself in C. It took less than 2 hours to get a version with the bells and whistles I wanted added. 我没有花时间试图找出问题所在,而是重新选择了替代方案 - 我自己在C中编写代码。花了不到2个小时的时间来获得一个我想要添加的铃声和口哨的版本。 I'm not sure whether I would have been able to get those into Perl as quickly, quite apart from the debugging of (my use of) File::Tail. 我不确定我是否能够快速地将这些内容导入Perl,除了(我使用的)File :: Tail的调试之外。 One oddity: I set my code to use 4096 byte buffers; 一个奇怪的是:我设置我的代码使用4096字节缓冲区; I found one line in the build process I monitor is over 5000 bytes long. 我发现构建过程中的一行我监视的长度超过5000字节。 Ah well - the code still uses 4096 byte buffers, but emits a dot for an over-long line like that. 好吧 - 代码仍然使用4096字节的缓冲区,但是为这样的超长线发出一个点。 Good enough for my purposes. 足够我的目的。 I also find that I need to allow for 5 minute pauses in the build output. 我还发现我需要在构建输出中允许5分钟的暂停。

Have you tried File::Tail to handle the actual tailing instead of trying to coerce <STDIN> to do the job? 您是否尝试过File :: Tail来处理实际拖尾而不是强迫<STDIN>来完成这项工作?

Or, if that piece does work, in what way is this failing? 或者,如果那件作品确实有效,那么失败的方式是什么?

The problem is most probably related to output buffering. 问题很可能与输出缓冲有关。 Have a read if you want a thorough explanation: 如果您想要彻底解释,请阅读:

http://www.pixelbeat.org/programming/stdio_buffering/ http://www.pixelbeat.org/programming/stdio_buffering/

In my case (on RHEL, I wanted tail -n 0 -f file | grep -m 1 pattern to terminate immediately when pattern occurs in the growing file), the proposed LD_PRELOADED library didn't help, neither did plain usage of the unbuffer utility from the Expect package. 在我的情况下(在RHEL上,我希望tail -n 0 -f file | grep -m 1 pattern在生长文件中出现模式时立即终止),建议的LD_PRELOADED库没有帮助,也没有明确使用unbuffer来自Expect包的实用程序。

But based on a blog post ( http://www.smop.co.uk/blog/index.php/2006/06/26/tail-f-and-awk/ ) I've discovered that redirecting input from tail launched in a subshell did the trick: 但基于博客文章( 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)

This was not as simple, though. 但这并不是那么简单。 While working in an interactive shell, the same command, when run remotely using SSH, still froze as usual: 在交互式shell中工作时,使用SSH远程运行时,同样的命令仍然像往常一样冻结:

ssh login@hostname 'grep -m 1 pattern <(tail -n 0 -f file)'

I've discovered that in this case, one must unbuffer tail's output using the unbuffer utility from Expect: 我发现在这种情况下,必须使用Expect中的unbuffer实用程序解除尾部的输出:

ssh login@hostname 'grep -m 1 pattern <(unbuffer -p tail -n 0 -f file)'

This must not be used on an interactive shell - unbuffer will cause an ioctl(raw): I/O error ! 不能在交互式shell上使用 - unbuffer会导致ioctl(raw): I/O error

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM