Избегайте буферизации при разборе стандартного вывода с помощью Perl

Я хочу анализировать вывод внешней программы (какой-то команды оболочки) построчно, используя Perl. Команда выполняется непрерывно, поэтому я помещаю ее в поток и использую общие переменные для связи с моей основной подпрограммой.

До сих пор мой код похож на

#!/usr/bin/perl

use warnings;
use strict;
use threads;
use threads::shared;

my $var :shared; $var="";

threads->create(
    sub {
        # command writes to stdout each ~100ms
        my $cmd = "<long running command> |";
        open(README, $cmd) or die "Can't run program: $!\n";
        while(<README>) {
            my $line = $_;
            # extract some information from line
            $var = <some value>;
            print "Debug\n";
        }
        close(README);
    }
);

while(1) {
    # evaluate variable each ~second
    print "$var\n";
    sleep 1;
}

Для некоторых команд это прекрасно работает, и строки обрабатываются так же, как они поступают. Вывод будет похож на:

...
Debug
Debug
...
<value 1>
...
Debug
Debug
...
<value 2>
...

Однако для других команд это ведет себя странно, и строки обрабатываются по блокам. Так $var не обновляется и Debug не печатается ни в течение некоторого времени. Затем, внезапно, результат будет (похож на):

...
<value 1>
<value 1>
<value 1>
...
Debug
Debug
Debug
...
<value 20>

а также $var устанавливается на последнее / текущее значение. Затем это повторяется. Разбор всегда задерживается и выполняется в блоках, пока $var не обновляется между.

Прежде всего: есть ли лучший / лучший способ для анализа вывода внешней программы (строка за строкой!), Кроме использования канала?

Если нет, как я могу избежать этого поведения?

Я прочитал, что с помощью autoflush(1); или же $|=1; может быть решением, но только для "выбранного в данный момент выходного канала". Как бы я использовал это в моем контексте?

Заранее спасибо.

2 ответа

Решение

Благодаря ikegami и Calle Dybedahl я нашел следующее решение для моей проблемы:

#!/usr/bin/perl

use warnings;
use strict;
use threads;
use threads::shared;
use sigtrap qw(handler exit_safely normal-signals stack-trace error-signals);
use IPC::Run qw(finish pump start);

# define shared variable
my $var :shared; $var="";

# define long running command
my @cmd = ('<long running command>','with','arguments');
my $in = '';
my $out = '';
# start harness
my $h = start \@cmd, '<pty<', \$in, '>pty>', \$out;

# create thread
my $thr = threads->create(
    sub {
        while (1) {
            # pump harness
            $h->pump;
            # extract some information from $out
            $var = <some value>;
            # empty output
            $out = '';
        }
    }
);

while(1) {
    # evaluate variable each ~second
    print "$var\n";
    sleep 1;
}

sub exit_safely {
    my ($sig) = @_;
    print "Caught SIG $sig\n";
    # harness has to be killed, otherwise
    # it will continue to run in background
    $h->kill_kill;
    $thr->join();
    exit(0);
}

exit(0);

В общем случае ваш скрипт не может изменить буферизацию вывода дочернего процесса. В некоторых конкретных случаях вы можете сделать это, запустив его с соответствующими переключателями, но это все.

Я бы порекомендовал вместо того, чтобы писать собственный код для запуска и чтения, переписать сценарий для использования IPC::Run модуль. Существует, чтобы решить именно такую ​​проблему. Документация не самая лучшая, но сам модуль хорошо протестирован и надежен.

Другие вопросы по тегам