2014-09-23 4 views
1

Я хочу проанализировать вывод внешней программы (некоторая команда оболочки) по строкам с помощью Perl. Команда запускается непрерывно, поэтому я помещаю ее в поток и использую общие переменные для связи с моей основной программой.Избегайте буферизации при анализе stdout с помощью 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

Это «долго работает команда» выход "s, который не покраснел. Есть утилита под названием «unbuffer», которая обманывает программы, которые используют соглашение буферизации строк при подключении к терминалу. – ikegami

+0

Есть ли способ контролировать поведение промывки команды? Но команда печатает каждые 100 мс до'stdout '. Где разница между shell stdout и трубопроводом на perl? – raidlman

+0

Я бы посмотрел на выполнение длинной команды из командной строки и трубопровода на 'perl -n script'. – marneborn

ответ

0

Благодаря Ikegami и Калле 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); 
+0

Я сомневаюсь, что вам нужно использовать '' ikegami

+0

Почему вы не используете вспомогательную ссылку вместо скалярной ссылки для вывода. Вам больше не нужно было перекачивать. – ikegami

0

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

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

+0

Как отправить команду в фоновый режим при разборе вывода в моей основной программе? Основываясь на документации, я попытался «start()» и «pump()« моя команда: 'my $ h = start \ @cmd, \ $ in, \ $ out; насос $ h; print $ out; '. Но вместо того, чтобы получить только одну строку, я получаю целый ряд строк. Если я повторю, чтобы нагнетать (без завершения), команда будет повторно выполнена, а не продолжена. Я что-то упускаю? – raidlman

+0

Как я уже упоминал, это помогает создание псевдо-tty возможностей IPC :: Run. Вы использовали его? – ikegami