2013-06-18 3 views
1

Я хотел бы выполнить внешнюю команду rtmpdump и прочитать ее STDOUT и STDERR отдельно, но не дождаться окончания такой команды, но прочитайте ее частичные выходы в массивах, когда они доступны .. .Чтение STDOUT и STDERR внешней команды без ожидания

Что такое безопасный способ сделать это на Perl?


Это код у меня есть, что работает «на линии» основе:

#!/usr/bin/perl 

use warnings; 
use strict; 
use Symbol; 
use IPC::Open3; 
use IO::Select; 

sub execute { 
    my($cmd) = @_; 
    print "[COMMAND]: $cmd\n"; 
    my $pid = open3(my $in, my $out, my $err = gensym(), $cmd); 
    print "[PID]: $pid\n"; 
    my $sel = new IO::Select; 
    $sel->add($out, $err); 
    while(my @fhs = $sel->can_read) { 
    foreach my $fh (@fhs) { 
     my $line = <$fh>; 
     unless(defined $line) { 
     $sel->remove($fh); 
     next; 
     } 
     if($fh == $out) { 
     print "[OUTPUT]: $line"; 
     } elsif($fh == $err) { 
     print "[ERROR] : $line"; 
     } else { 
     die "[ERROR]: This should never execute!"; 
     } 
    } 
    } 
    waitpid($pid, 0); 
} 

Но приведенный выше код работает только в текстовом режиме, я считаю. Чтобы использовать rtmpdump в качестве команды, мне нужно собрать частичные выходы в двоичном режиме, поэтому не читайте STDOUT по строке, как указано в приведенном выше коде.

Двоичный выход STDOUT должен храниться в переменной, а не печатать.

ответ

1

Использование блокирующих функций (например, так называемый readline<>, read, и т.д.) внутри петли select бросает вызов использование select.

$sel->add($out, $err); 

my %bufs; 
while ($sel->count) { 
    for my $fh ($sel->can_read) { 
     my $rv = sysread($fh, $bufs{$fh}, 128*1024, length($bufs{$fh})); 

     if (!defined($rv)) { 
     # Error 
     die $! ; 
     } 

     if (!$rv) { 
     # Eof 
     $sel->remove($fh); 
     next; 
     }   

     if ($fh == $err) { 
     while ($bufs{$err} =~ s/^(.*\n)//) { 
      print "[ERROR] $1"; 
     } 
     } 
    } 
} 

print "[ERROR] $bufs{$err}\n" if length($bufs{$err}); 

waitpid($pid, 0); 

... do something with $bufs{$out} ... 

Но было бы гораздо проще использовать IPC::Run.

use IPC::Run qw(run); 

my ($out_buf, $err_buf); 
run [ 'sh', '-c', $cmd ], 
    '>', \$out_buf, 
    '2>', sub { 
     $err_buf .= $_[0]; 
     while ($err_buf =~ s/^(.*\n)//) { 
     print "[ERROR] $1"; 
     } 
    }; 

print "[ERROR] $err_buf\n" if length($err_buf); 

... do something with $out_buf ... 
+0

Я должен уточнить одну проблему: кроме хранения данных STDOUT для переменной мне нужно иметь возможность обрабатывать эти частичные данные, когда они доступны. Другими словами, некоторые функции должны вызывать новые данные STDOUT каждый раз, когда они доступны. –

+0

@ Ωmega замените '\ $ out_buf' на sub ref –

+0

Как и @mpapec, следуйте примеру STDERR. – ikegami

1

Если вы используете систему POSIX, попробуйте использовать Expect.pm. Это именно та проблема, которую она предназначена для решения, а также упрощает задачу отправки нажатий клавиш в порожденный процесс.

+0

Спасибо за ответ, но этот вид решения является не то, что я ищу ... –

+0

я перепутал - Вы можете объяснить, почему Expect.pm не решить вашу проблему? Или вы специально хотите создать «с нуля» решение без использования существующих библиотек? –