22

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

4

5 に答える 5

31

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";
}
于 2012-05-26T10:44:35.563 に答える
11

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

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

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

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

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

コマンドでパイプを開くと-(つまり、の1つまたは2つの引数形式のいずれ|-かを指定する)、暗黙的に行われるため、openは2回戻ります。親プロセスでは、子プロセスのpidを返します。子プロセスでは、(定義された)を返します。またはを使用して、が成功したかどうかを判断します。-|openfork0defined($pid)//open

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

  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: $!";
}

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

于 2012-05-26T17:27:57.090 に答える
3

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

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

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

cat file | your_perl_prog.pl

(編集)これをもっと注意深く説明しようと思います...

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

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

your_perl_prog.plを変更して、シバン行に「-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
于 2014-10-11T22:42:39.217 に答える
3

@GregBaconの答えを変更せずに拡張したいと思います。

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

また、ActiveState perlを実行しているバージョンでも機能するようにする必要がありましたが、そのバージョンのperlは機能しません。この質問を参照してくださいActiveStatePerlで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: $!";
于 2015-11-19T19:58:29.170 に答える
1

OPに必要なことを実行する最も簡単な方法は、次のように、外部プロセッサが実行されるまで出力を保持するために一時ファイルを使用することです。

open ToTemp, "|/usr/bin/tac>/tmp/MyTmp$$.whee" or die "open the tool: $!";
print ToTemp $TheMessageWhateverItIs;
close ToTemp;
my $Result = `cat /tmp/MyTmp$$.whee`;  # or open and read it, or use File::Slurp, etc
unlink "/tmp/MyTmp$$.whee"; 

もちろん、これはインタラクティブなものでは機能しませんが、コルーチンは元の質問の範囲外のようです。

于 2020-10-30T22:32:27.357 に答える