2009-08-05 1 views
4

나는 약간의 bash 스크립트를 사용하고 있지만, 다운로드 속도, ETA 및 이와 유사한 정보를 추출하는 데 몇 시간이 걸릴 수 있습니다. 이 정보를 펄에서 포착 할 필요가 있지만 문제가 생기고 출력물을 라인별로 읽을 수 없다.Perl에서 외부 명령의 출력을 실시간으로 어떻게 읽을 수 있습니까?

어떤 도움이 필요합니까?

EDIT :이 부분을 좀더 자세히 설명하기 위해 나는 서로 나란히있는 여러 bash 스크립트를 실행하고 있는데 gtk를 사용하여 gtk를 사용하여 편리한 진행률 막대를 생성하고자합니다. 현재 실행하고자하는 모든 bash 스크립트에 대해 2 개의 스레드를 실행 중입니다. 그래픽 정보를 업데이트하기위한 하나의 마스터 스레드입니다. 그것은 다음과 같이 보입니다 (가능한 한 많이 줄일 수 있습니다) :

my $command1 = threads->create(\&runCmd, './bash1', \@out1); 
    my $controll1 = threads->create(\&monitor, $command1, \@out1); 
    my $command1 = threads->create(\&runCmd, 'bash2', \@out2); 
    my $controll2 = threads->create(\&monitor, $command2, \@out2); 

    sub runCmd{ 
    my $cmd = shift; 
    my @bso = shift; 
    @bso = `$cmd` 
    } 
    sub monitor{ 
    my $thrd = shift; 
    my @bso = shift; 
    my $line; 
    while($thrd->is_running()){ 
     while($line = shift(@bso)){ 
     ## I check the line and do things with it here 
     } 
     ## update anything the script doesn't tell me here. 
     sleep 1;# don't cripple the system polling data. 
    } 
    ## thread quit, so we remove the status bar and check if another script is in the queue, I'm omitting this here. 
    } 
+1

실제로 스레드 대신 POE와 같은 적절한 이벤트 루프를 사용해야합니다. 당신은 POE :: Wheel :: Run으로 훨씬 더 성공적으로 성공할 것입니다. (AnyEvent :: Subprocess를 권하고 싶지만, 주요한 리펙토링을 겪고 있으며 즉시 문제를 해결하지는 못할 것입니다.) – jrockway

답변

7
대신 스레드의

, 그리고``, 사용 :

open my $fh, '-|', 'some_program --with-options'; 

열려있는 여러 파일 핸들이 그들로부터 데이터를 폴링 IO::Select을 사용하여 다음과 (많은 프로그램이 많은 당신이 실행해야합니다) 이런 식으로.

단순한 예입니다.

의 내가 다음과 같습니다 쉘 스크립트를 가정 해 봅시다 :

 
=> ./test.sh 
from 26513 : Fri Aug 7 08:48:06 CEST 2009 
from 26513 : Fri Aug 7 08:48:07 CEST 2009 
from 26513 : Fri Aug 7 08:48:08 CEST 2009 
from 26513 : Fri Aug 7 08:48:09 CEST 2009 
from 26513 : Fri Aug 7 08:48:10 CEST 2009 

을 지금,이 multi-test.pl 쓸 수 :

#!/usr/bin/perl -w 
use strict; 
use IO::Select; 

my $s = IO::Select->new(); 

for (1..2) { 
    open my $fh, '-|', './test.sh'; 
    $s->add($fh); 
} 

while (my @readers = $s->can_read()) { 
    for my $fh (@readers) { 
     if (eof $fh) { 
      $s->remove($fh); 
      next; 
     } 
     my $l = <$fh>; 
     print $l; 
    } 
} 

=> cat test.sh 
#!/bin/bash 
for i in $(seq 1 5) 
do 
    sleep 1 
    echo "from $$ : $(date)" 
done 

이 출력은 다음과 같을 수 있어요 보시다시피 포크가없고 실이 없습니다. 그리고 이것은 작동하는 방법이다 : (당신은뿐만 아니라 STDERR을 캡처하려는 경우 또는 IPC::Open3)

 
=> time ./multi-test.pl 
from 28596 : Fri Aug 7 09:05:54 CEST 2009 
from 28599 : Fri Aug 7 09:05:54 CEST 2009 
from 28596 : Fri Aug 7 09:05:55 CEST 2009 
from 28599 : Fri Aug 7 09:05:55 CEST 2009 
from 28596 : Fri Aug 7 09:05:56 CEST 2009 
from 28599 : Fri Aug 7 09:05:56 CEST 2009 
from 28596 : Fri Aug 7 09:05:57 CEST 2009 
from 28599 : Fri Aug 7 09:05:57 CEST 2009 
from 28596 : Fri Aug 7 09:05:58 CEST 2009 
from 28599 : Fri Aug 7 09:05:58 CEST 2009 

real 0m5.128s 
user 0m0.060s 
sys  0m0.076s 
+0

이것은 지금까지 가장 깨끗한 해결책 인 것 같습니다. 고마워요. 내 현재 (해키하고 작동하지 않는) 코드가 제공 한대로 완벽하게 작동하려면 너무 많은 작업을해서는 안됩니다. 다시 한 번 감사드립니다. – scragar

1

예, 가능합니다.

while (<STDIN>) { print "Line: $_"; } 

문제는 일부 응용 프로그램은 선으로 정보 라인에서 분출하지만이 완료 될 때까지 한 줄을 업데이트하지 않습니다. 그것은 당신의 경우입니까?

+0

라인이 표준에서 오지는 않지만 open을 사용하여 스크립트에 대한 라인을 열 수 있습니다 명령, 그렇게 작동합니다. 내 문제는 입력이 없을 때 나는 그 라인이 무엇인지를 입력 할 때까지 기다릴 때와 다른 것을 할 수 있기를 원한다는 것이다. 현재이 작업을 수행하기 위해 매우 복잡한 회귀 콤보를 실행 중입니다 (이 질문을 표시하도록 업데이트 할 예정입니다). – scragar

+1

Windows에서 스크립트를 실행하지 않는 한 select를 사용하여 파일 설명자에서 사용 가능한 데이터가 있는지 테스트 할 수 있습니다. 대략 다음과 같이 : while (1) { my $ rin = ''; vec ($ rin, fileno (STDIN), 1) = 1; my ($ nfound, $ timeleft) = 선택 ($ rin, undef, undef, 0); if ($ nfound) { my $ data; "데이터를 얻었습니다!"\ n "; sysread STDIN, $ data, 1024; "DATA : $ data \ n"; } else { print "wait ... \ n"; sleep (1); } } –

3

백틱 및 qx // 연산자는 모두 하위 프로세스가 완료 될 때까지 차단됩니다. 파이프에서 bash 스크립트를 열어야합니다. 비 블로킹 (non-blocking)이 필요한 경우 open2 또는 open3을 사용하여 파일 핸들로 열고 핸들을 select()에 넣고 읽을 수있게 될 때까지 기다립니다.

방금 ​​비슷한 문제가 발생했습니다. qx //로 열었던 서비스는 매우 오래 실행되었습니다 (몇 주 동안 실행될 수있는 서비스). 문제는이 프로그램의 출력이 결국 메모리 제한을 초과한다는 것입니다 (내 아키텍처에서 약 2.5G). 파이프의 하위 명령을 연 다음 출력의 마지막 1000 줄만 저장하여 해결했습니다. 이렇게하면 qx // 양식은 명령이 완료되면 출력 만 인쇄하지만 파이프 양식은 출력이 출력 될 때 인쇄 할 수 있다는 것을 알게되었습니다.

나는 편리한 코드가 없지만 내일까지 기다릴 수 있다면 내가 한 것을 게시 할 것이다.

1

여기에는 진행률 막대를 표시하는 GTK2 코드가 있습니다.

#!/usr/bin/perl 
use strict; 
use warnings; 

use Glib qw/TRUE FALSE/; 
use Gtk2 '-init'; 

my $window = Gtk2::Window->new('toplevel'); 
$window->set_resizable(TRUE); 
$window->set_title("command runner"); 

my $vbox = Gtk2::VBox->new(FALSE, 5); 
$vbox->set_border_width(10); 
$window->add($vbox); 
$vbox->show; 

# Create a centering alignment object; 
my $align = Gtk2::Alignment->new(0.5, 0.5, 0, 0); 
$vbox->pack_start($align, FALSE, FALSE, 5); 
$align->show; 

# Create the Gtk2::ProgressBar and attach it to the window reference. 
my $pbar = Gtk2::ProgressBar->new; 
$window->{pbar} = $pbar; 
$align->add($pbar); 
$pbar->show; 

# Add a button to exit the program. 
my $runbutton = Gtk2::Button->new("Run"); 
$runbutton->signal_connect_swapped(clicked => \&runCommands, $window); 
$vbox->pack_start($runbutton, FALSE, FALSE, 0); 

# This makes it so the button is the default. 
$runbutton->can_default(TRUE); 

# This grabs this button to be the default button. Simply hitting the "Enter" 
# key will cause this button to activate. 
$runbutton->grab_default; 
$runbutton->show; 

# Add a button to exit the program. 
my $closebutton = Gtk2::Button->new("Close"); 
$closebutton->signal_connect_swapped(clicked => sub { $_[0]->destroy;Gtk2->main_quit; }, $window); 
$vbox->pack_start($closebutton, FALSE, FALSE, 0); 

$closebutton->show; 

$window->show; 

Gtk2->main; 

sub pbar_increment { 
    my ($pbar, $amount) = @_; 

    # Calculate the value of the progress bar using the 
    # value range set in the adjustment object 
    my $new_val = $pbar->get_fraction() + $amount; 

    $new_val = 0.0 if $new_val > 1.0; 

    # Set the new value 
    $pbar->set_fraction($new_val); 
} 

sub runCommands { 
     use IO::Select; 

     my $s = IO::Select->new(); 

     for (1..2) { 
      open my $fh, '-|', './test.sh'; 
      $s->add($fh); 
     } 

     while (my @readers = $s->can_read()) { 
      for my $fh (@readers) { 
       if (eof $fh) { 
        $s->remove($fh); 
        next; 
       } 
       my $l = <$fh>; 
       print $l; 
       pbar_increment($pbar, .25) if $l =~ /output/; 
      } 
     } 
    } 

내가 내 외부 명령을 기록하기 위해이 서브 루틴 및 방법을 사용하여 추가 정보를

+0

오. 나의. 그것은 과잉 공격의 정의입니다. –

+0

생생하고 배우십시오 ... SO에 관한 더 나은 점 중 하나는 제가 여기에서 가장 현명한 사람이 아니라는 것입니다. –

1

에 대한 the perl GTK2 docs를 참조하십시오. 서브 루틴 현재

open($logFileHandle, "mylogfile.log"); 

logProcess($logFileHandle, "ls -lsaF", 1, 0); #any system command works 

close($logFileHandle); 

과 : 다음과 같이이라고

#****************************************************************************** 
# Sub-routine: logProcess() 
#  Author: Ron Savage 
#  Date: 10/31/2006 
# 
# Description: 
# This sub-routine runs the command sent to it and writes all the output from 
# the process to the log. 
#****************************************************************************** 
sub logProcess 
    { 
    my $results; 

    my ($logFileHandle, $cmd, $print_flag, $no_time_flag) = @_; 
    my $logMsg; 
    my $debug = 0; 

    if ($debug) { logMsg($logFileHandle,"Opening command: [$cmd]", $print_flag, $no_time_flag); } 
    if (open($results, "$cmd |")) 
     { 
     while (<$results>) 
     { 
     chomp; 
     if ($debug) { logMsg($logFileHandle,"Reading from command: [$_]", $print_flag, $no_time_flag); } 
     logMsg($logFileHandle, $_, $print_flag, $no_time_flag); 
     } 

     if ($debug) { logMsg($logFileHandle,"closing command.", $print_flag, $no_time_flag); } 
     close($results); 
     } 
    else 
     { 
     logMsg($logFileHandle, "Couldn't open command: [$cmd].") 
     } 
    } 

#****************************************************************************** 
# Sub-routine: logMsg() 
#  Author: Ron Savage 
#  Date: 10/31/2006 
# 
# Description: 
# This sub-routine prints the msg and logs it to the log file during the 
# install process. 
#****************************************************************************** 
sub logMsg 
    { 
    my ($logFileHandle, $msg, $print_flag, $time_flag) = @_; 
    if (!defined($print_flag)) { $print_flag = 1; } 
    if (!defined($time_flag)) { $time_flag = 1; } 

    my $logMsg; 

    if ($time_flag) 
     { $logMsg = "[" . timeStamp() . "] $msg\n"; } 
    else 
     { $logMsg = "$msg\n"; } 

    if (defined($logFileHandle)) { print $logFileHandle $logMsg; } 

    if ($print_flag) { print $logMsg; } 
    } 
2

은 당신이 할 수있는 몇 가지의 perlipc (프로세스 간 통신)를 참조하십시오. Piped opens과 IPC :: Open3은 편리합니다.

0

입력 및 출력을 완벽하게 제어와 자식 프로세스를 실행하는 가장 간단한 방법은 IPC::Open2 모듈이지만, 문제 한 번에 여러 항목을 처리하려는 경우 또는 특히 GUI에서 수행하려는 경우 차단해야합니다. 만약 <$fh> 타입을 읽는다면, 입력이있을 때까지 차단 될 것입니다. 잠재적으로 당신의 전체 UI를 쐐기로 고정시킬 것입니다. 자식 프로세스가 대화식 인 경우, 자식 프로세스와 부모 프로세스가 교착 상태가되어 다른 프로세스로부터의 입력을 기다리고 있기 때문에 더욱 악화됩니다. 자신의 select 루프를 작성하고 논 블로킹 I/O를 수행 할 수는 있지만 실제로 가치가있는 것은 아닙니다. 내 제안은 자식 프로세스와의 인터페이스에 POE, POE::Wheel::Run을 사용하고 POE를 GTK runloop에 포함시키기 위해 POE::Loop::Gtk을 사용하는 것입니다.