【问题标题】:How to read to and write from a pipe in Perl?如何在 Perl 中读取和写入管道?
【发布时间】:2012-06-01 16:18:14
【问题描述】:

我需要修改现有的 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 不支持这种方式,因为它可能会陷入死锁,我可以理解。但是那我该怎么做呢?

【问题讨论】:

  • perlipc 手册页有讨论和大量不同方法的示例。

标签: perl pipe


【解决方案1】:

您可以使用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";
}

【讨论】:

  • 完美运行。谢谢。使用 open2 而不是 open3 因为我不关心标准错误。
  • 值得注意的是,open2 的前两个参数与open3 正好相反!例如,open2(\*CHLD_OUT, \*CHLD_IN, 'cat')
【解决方案2】:

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

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

创建此管道很简单。 Perl 的open“Safe pipe opens” section of the perlipc documentation 中有一个有用的模式。

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

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

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

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

【讨论】:

    【解决方案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 = &lt;&gt;;' 读取每一行并处理/打印

    然后用

    调用该批次
    cat sourcefile |filter|your_perl_prog.pl
    

    【讨论】:

      【解决方案4】:

      我想扩展@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: $!";
      

      【讨论】:

        【解决方案5】:

        最简单的——不涉及所有这些很酷的内部——做 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"; 
        

        当然,这不适用于交互的东西,但协同例程似乎超出了原始问题的范围。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-01-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多