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

这涉及系统编程,因此它不仅仅是一个基本问题。如所写,您的主程序不需要与外部程序进行全双工交互。数据流向一个方向传播,即

字符串→外部程序→主程序

创建此管道很简单。Perlopen有一个有用的模式,在 perlipc 文档的“安全管道打开”部分中进行了说明

进程间通信的另一种有趣方法是让您的单个程序成为多进程并在您自己之间甚至彼此之间进行通信。该open函数将接受一个文件参数,或者"-|""|-"一件非常有趣的事情:它派生一个连接到您打开的文件句柄的孩子。孩子正在运行与父母相同的程序。例如,这对于在假定的 UID 或 GID 下运行时安全打开文件很有用。如果您打开一个减号管道,您可以写入您打开的文件句柄,您的孩子会在他的STDIN. 如果你从减号打开一个管道,你可以从你打开的文件句柄中读取你的孩子写给他的任何内容STDOUT

这是一个open涉及管道的管道,它为返回值提供了细微差别。perlfunc 文档open解释了。

如果你在命令上打开一个管道-(即,指定一个|--|两个参数形式open),一个隐式fork完成,所以 open 返回两次:在父进程中它返回子进程的 pid,并在子进程中返回 (a defined) 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: $!";
}

感谢有机会写出这么有趣的程序!

于 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 以便 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
于 2014-10-11T22:42:39.217 回答
3

我想扩展@Greg Bacon 的答案而不改变它。

我必须执行类似的操作,但是想在没有给定/何时命令的情况下进行编码,并且还发现缺少明确的 exit() 调用,因为在示例代码中它失败并退出了。

我还必须让它在运行 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: $!";
于 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 回答