Как мне реализовать 'tail -f' с тайм-аутом при чтении в Perl? - PullRequest
4 голосов
/ 14 января 2009

Мой вопрос - противоположность Как я могу обработать ввод немедленно, вместо ожидания новой строки . Я хочу продолжить чтение растущего файла журнала, но остановлюсь после того, как файл не будет увеличен в течение указанного количества секунд.

Я нашел Sys :: AlarmCall в CPAN и попытался, как показано ниже, но он не истекает при запуске:

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 часов, чтобы получить версию с добавленными прибамбасами и свистками. Я не уверен, смог бы ли я получить их в Perl так быстро, совершенно отдельно от отладки (моего использования) File :: Tail. Одна странность: я установил свой код для использования 4096-байтовых буферов; Я обнаружил, что одна строка в процессе сборки, которую я отслеживаю, имеет длину более 5000 байт. Ну хорошо - код все еще использует 4096-байтовые буферы, но выдает точку для такой длинной строки. Достаточно хорошо для моих целей. Я также обнаружил, что мне нужно разрешить 5-минутные паузы в выводе сборки.

Ответы [ 2 ]

6 голосов
/ 14 января 2009

Вы пытались File :: Tail обрабатывать фактический хвост вместо того, чтобы пытаться выполнить работу?

Или, если этот кусок работает, каким образом это терпит неудачу?

3 голосов
/ 25 февраля 2010

Проблема, скорее всего, связана с буферизацией вывода. Прочитайте, если хотите подробное объяснение:

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/) Я обнаружил, что перенаправление ввода из tail, запущенного в подоболочке, добилось цели:

grep -m 1 pattern <(tail -n 0 -f file)

Хотя это было не так просто. При работе в интерактивной оболочке та же команда при удаленном запуске с использованием SSH по-прежнему зависала как обычно:

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

Я обнаружил, что в этом случае нужно отменить буферизацию вывода tail, используя утилиту unbuffer из Expect:

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

Этот не должен использоваться в интерактивной оболочке - снятие буфера вызовет ioctl(raw): I/O error!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...