2012-05-26 117 views
13

我是Perl noob,所以請原諒這個基本問題。我需要修改現有的Perl程序。我想通過外部程序輸入一個字符串(可以包含多行)並讀取該程序的輸出。所以這個外部程序用來修改字符串。讓我們簡單地使用cat作爲過濾程序。我試過這樣,但它不起作用。 (中cat輸出發送到stdout,而不是由perl被讀取。)如何在Perl中讀取和寫入管道?

#!/usr/bin/perl 

open(MESSAGE, "| cat |") or die("cat failed\n"); 
print MESSAGE "Line 1\nLine 2\n"; 
my $message = ""; 
while (<MESSAGE>) 
{ 
    $message .= $_; 
} 
close(MESSAGE); 
print "This is the message: $message\n"; 

我讀過,這是不被Perl支持,因爲它可以在一個僵局結束了,我可以理解。但是,我該怎麼做呢?

+0

的'perlipc'手冊頁有不同的方法實例的討論和充足。 – tripleee

回答

22

您可以使用IPC::Open3實現與孩子的雙向溝通​​。

use strict; 
use IPC::Open3; 

my $pid = open3(\*CHLD_IN, \*CHLD_OUT, \*CHLD_ERR, 'cat') 
    or die "open3() failed $!"; 

my $r; 

for(my $i=1;$i<10;$i++) { 
    print CHLD_IN "$i\n"; 
    $r = <CHLD_OUT>; 
    print "Got $r from child\n"; 
} 
+0

完美地工作。謝謝。使用open2而不是open3,因爲我不關心stderr。 – kayahr

+2

值得注意的是,'open2'的前兩個參數與'open3'相反!例如,它是'open2(\ * CHLD_OUT,\ * CHLD_IN,'cat')'。 –

8

這涉及系統編程,所以它不僅僅是一個基本的問題。正如所寫,您的主程序不需要與外部程序進行全雙工交互。數據流傳播的一個方向,即

串→外部程序→主程序

創建這條管道是直接的。 Perl的open有一個在「Safe pipe opens」 section of the perlipc documentation中解釋的有用模式。

進程間通信的另一個有趣的方法是讓你的單個程序進入多進程並在兩者之間進行通信,甚至是在你們自己之間進行通信。 open函數將接受文件參數"-|""|-"做一個非常有趣的事情:它分叉一個連接到您打開的文件句柄的孩子。孩子正在運行與父母相同的程序。例如,這對於在假定的UID或GID下運行時安全地打開文件很有用。如果你打開一個減號管道,你可以寫入你打開的文件句柄,你的孩子會在他的STDIN中找到它。如果你從零開始打開一個管道,你可以從你打開的文件句柄讀取任何你的孩子寫給他的STDOUT

這是一個涉及管道的open,它使返回值變得細微。 perlfunc documentation on open解釋。

如果您在命令-打開一個管道(即中,指定|--|與一個或兩個參數的形式open),隱式fork完成,所以開的回報兩次:在父進程返回子進程的PID,並在子進程中返回(定義的)0。使用defined($pid)//來確定open是否成功。

要創建腳手架,我們從右到左的順序使用openfork一個新的進程的每一步工作。

  1. 您的主程序已在運行。
  2. 接下來,fork這個過程最終會成爲外部程序。
  3. 的進程中從第2步
    1. 首先fork字符串印刷工藝,使輸出我們的STDIN到達。
    2. 然後exec執行其轉換的外部程序。
  4. 讓字符串打印機完成其工作,然後exit,這將啓動一個新的水平。
  5. 回到主程序中,讀取轉換結果。

隨着所有這些設置,所有你需要做的就是將你的建議植入底部,科布先生。

#! /usr/bin/env perl 

use 5.10.0; # for defined-or and given/when 
use strict; 
use warnings; 

my @transform = qw(tr [A-Za-z] [N-ZA-Mn-za-m]); # rot13 
my @inception = (
    "V xabj, Qnq. Lbh jrer qvfnccbvagrq gung V pbhyqa'g or lbh.", 
    "V jnf qvfnccbvagrq gung lbh gevrq.", 
); 

sub snow_fortress { print map "$_\n", @inception } 

sub hotel { 
    given (open STDIN, "-|" // die "$0: fork: $!") { #/StackOverflow hiliter 
    snow_fortress when 0; 
    exec @transform or die "$0: exec: $!"; 
    } 
} 

given (open my $fh, "-|" // die "$0: fork: $!") { 
    hotel when 0; 

    print while <$fh>; 
    close $fh or warn "$0: close: $!"; 
} 

感謝您有機會編寫這樣一個有趣的程序!

2

您可以使用命令行-n開關,有效地包裝現有的程序代碼在while循環......看看手冊頁-n:

LINE: 
      while (<>) { 
       ...    # your program goes here 
      } 

然後你可以使用操作系統的管機制直接

cat file | your_perl_prog.pl 

(編輯) 我會試着解釋這更仔細...

的問題是不清楚哪一部分在p erl程序播放:過濾或最後階段。這在任何情況下都有效,所以我會認爲它是後者。

'your_perl_prog.pl'是您現有的代碼。我會打電話給你的過濾器程序'過濾器'。

修改your_perl_prog.pl使shebang行有一個附加的 '-n' 開關:!#在/ usr/bin中/ perl的-n或#/斌/包膜 「的perl -n」

這有效在your_perl_prog.pl卻將一段時間(<>){}環周圍的代碼

添加一個BEGIN塊打印頭:

BEGIN {print "HEADER LINE\n");} 

可以與'$line = <>;'和處理/打印

讀每一行

然後用

cat sourcefile |filter|your_perl_prog.pl 
+0

請解釋回覆票,以便我可以捍衛答案 – Rondo

+0

我添加了一個編輯來解釋我的意思 – Rondo

1

我想在@格雷格培根的答案擴大而不改變它調用許多。

我不得不執行類似的東西,但想代碼,而無需 給定/命令時,也發現有明確的退出()調用 它告吹並退出樣本代碼,因爲缺少。

我還必須使它在運行ActiveState perl, 的版本上工作,但該版本的perl不起作用。 看到這個問題How to read to and write from a pipe in perl with ActiveState Perl?

#! /usr/bin/env perl 

use strict; 
use warnings; 

my $isActiveStatePerl = defined(&Win32::BuildNumber); 

sub pipeFromFork 
{ 
    return open($_[0], "-|") if (!$isActiveStatePerl); 
    die "active state perl cannot cope with dup file handles after fork"; 

    pipe $_[0], my $child or die "cannot create pipe"; 
    my $pid = fork(); 
    die "fork failed: $!" unless defined $pid; 
    if ($pid) {   # parent 
     close $child; 
    } else {   # child 
     open(STDOUT, ">&=", $child) or die "cannot clone child to STDOUT"; 
     close $_[0]; 
    } 
    return $pid; 
} 


my @transform = qw(tr [A-Za-z] [N-ZA-Mn-za-m]); # rot13 
my @inception = (
    "V xabj, Qnq. Lbh jrer qvfnccbvagrq gung V pbhyqa'g or lbh.", 
    "V jnf qvfnccbvagrq gung lbh gevrq.", 
); 

sub snow_fortress { print map "$_\n", @inception } 

sub hotel 
{ 
    my $fh; 
    my $pid = pipeFromFork($fh);  # my $pid = open STDIN, "-|"; 
    defined($pid) or die "$0: fork: $!"; 
    if (0 == $pid) { 
     snow_fortress; 
     exit(0); 
    } 
    open(STDIN, "<&", $fh) or die "cannot clone to STDIN"; 
    exec @transform or die "$0: exec: $!"; 
} 

my $fh; 
my $pid = pipeFromFork($fh);   # my $pid = open my $fh, "-|"; 
defined($pid) or die "$0: fork: $!"; 
if (0 == $pid) { 
    hotel; 
    exit(0); 
} 

print while <$fh>; 
close $fh or warn "$0: close: $!";