web-dev-qa-db-ja.com

Perlでパイプを読み書きする方法は?

私はPerlの初心者なので、この基本的な質問を許しません。既存のPerlプログラムを変更する必要があります。文字列(複数の行を含めることができる)を外部プログラムにパイプし、このプログラムからの出力を読み取りたい。したがって、この外部プログラムは文字列を変更するために使用されます。単純にcatをフィルタープログラムとして使用します。このように試してみましたが、うまくいきません。 (catの出力は、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ではサポートされていません。しかし、それではどうすればよいですか?

17
kayahr

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";
}
26
tuxuday

これにはシステムプログラミングが含まれるため、基本的な質問以上のものです。書かれているように、メインプログラムは外部プログラムとの全二重通信を必要としません。データフローは一方向、つまり

文字列→外部プログラム→メインプログラム

このパイプラインの作成は簡単です。 Perlのopenには、perlipcドキュメントの 「Safe pipe opens」セクションで説明されている便利なモードがあります

プロセス間通信のもう1つの興味深いアプローチは、単一のプログラムをマルチプロセスにして、自分自身の間で、または自分自身の間でさえ通信することです。 open関数は、_"-|"_または_"|-"_のいずれかのファイル引数を受け入れて、非常に興味深い処理を実行します。開いたファイルハンドルに接続された子をフォークします。子は親と同じプログラムを実行しています。これは、たとえば、想定されたUIDまたはGIDで実行しているときにファイルを安全に開く場合に役立ちます。マイナスへのパイプを開くと、開いたファイルハンドルに書き込むことができ、子供はSTDINでそれを見つけます。マイナスからパイプを開くと、子供がSTDOUTに書き込んだものをすべて、開いたファイルハンドルから読み取ることができます。

これは、パイプを含むopenであり、戻り値にニュアンスを与えます。 perlfuncのドキュメントopen で説明しています。

コマンド_-_でパイプを開く場合(つまり、openの1つまたは2つの引数の形式で_|-_または_-|_を指定します)、暗黙のforkが完了すると、openは2度戻ります。親プロセスでは子プロセスのpidを返し、子プロセスでは(定義済み)_0_を返します。 openが成功したかどうかを判断するには、defined($pid)または_//_を使用します。

足場を作成するには、各ステップでopenからforkの新しいプロセスを使用して、右から左の順序で作業します。

  1. メインプログラムは既に実行中です。
  2. 次に、forkは最終的に外部プログラムになるプロセスです。
  3. ステップ2のプロセスの内部
    1. まず、出力をforkに到達させるための文字列印刷プロセスSTDINです。
    2. 次に、変換を実行する外部プログラムをexecします。
  4. String-printerに作業を依頼し、次に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: $!";
}
_

そのような楽しいプログラムを書く機会をありがとう!

10
Greg Bacon

-nコマンドラインスイッチを使用すると、whileループで既存のプログラムコードを効果的にラップできます。-nのマニュアルページをご覧ください。

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

次に、オペレーティングシステムのパイプメカニズムを直接使用できます。

cat file | your_Perl_prog.pl

(編集)私はこれをより注意深く説明しようとします...

質問は、Perlプログラムがどのような役割を果たしているかについては明確ではありません:フィルターまたは最終段階。これはどちらの場合でも機能するので、後者だと思います。

「your_Perl_prog.pl」は既存のコードです。フィルタープログラムを 'filter'と呼びます。

Your_Perl_prog.plを変更して、Shebang行に「-n」スイッチを追加します。#!/ usr/bin/Perl -nまたは#!/ bin/env "Perl -n"

これにより、your_Perl_prog.plのコードの周りにwhile(<>){}ループが効果的に配置されます

bEGINブロックを追加してヘッダーを印刷します。

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

'$line = <>;'で各行を読み取り、処理/印刷できます

次に、ロットを呼び出します

cat sourcefile |filter|your_Perl_prog.pl
2
Rondo

@Greg Baconの答えを変えずに拡大したいと思います。

私は同じようなものを実行する必要がありましたが、given/whenコマンドなしでコーディングしたいと思いました。また、サンプルコードでは抜け落ちて終了したため、exit()呼び出しが明示的に欠落していることもわかりました。

ActiveState Perlを実行しているバージョンでも動作させる必要がありましたが、そのバージョンのPerlは動作しません。この質問を参照してください Perlで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: $!";
1
codeDr