在perl中打开管道时直接STDERR

时间:2010-08-15 07:32:44

标签: r perl pipe execution

我使用open( my $command_out, "-|", $command_string )执行命令并动态处理其输出(无需等待命令先完成,如system()中所示)。

我注意到当我以这种方式调用一些R脚本时,一些R消息被打印到屏幕上(例如Loading required package: ...)。我想这是因为R将此输出发送到stderr(?虽然这些不是真正的错误)。

是否可以在$command_out时将此输出定向到open(),以便屏幕保持干净?

5 个答案:

答案 0 :(得分:10)

说你的R程序是

#! /usr/bin/env r

require(Hmisc)

cat(argv[1], "\n")

结果令人惊讶的是:

$ ./prog.r foo
Loading required package: Hmisc
Loading required package: methods
Loading required package: survival
Loading required package: stats
Loading required package: utils
Loading required package: graphics
Loading required package: splines

Attaching package: 'Hmisc'


    The following object(s) are masked from package:survival :

     untangle.specials 


    The following object(s) are masked from package:base :

     format.pval,
     round.POSIXt,
     trunc.POSIXt,
     units 

foo

从此处,您可以放弃标准错误或将其与其他流合并。

丢弃标准错误

丢弃标准错误的一种方法是使用2>/dev/null的shell重定向。这是一种通用机制,2是standard error's file descriptor。例如:

#! /usr/bin/perl

use warnings;
use strict;

open my $command_out, "-|", "./prog.r foo 2>/dev/null"
  or die "$0: could not start prog.r";

while (<$command_out>) {
  print "got: $_";
}

shell还将处理backtick or qx//个表达式

#! /usr/bin/perl

use warnings;
use strict;

(my $command_out = `./prog.r foo 2>/dev/null`) =~ s/^/got: /mg;
print $command_out;

并将标量命令传递给system

#! /usr/bin/perl

use warnings;
use strict;

system("./prog.r foo 2>/dev/null") == 0
  or warn "$0: prog.r exited " . ($? >>8);

对于所有这些,输出是

$ ./prog.pl 
got: foo

但有时你希望shell将你的肮脏手套放在你的命令上。也许它包含你不想处理转义的shell元字符或引号。这是safe pipe-open有用的时候:

#! /usr/bin/perl

use warnings;
use strict;

my $pid = open my $command_out, "-|";
die "$0: fork: $!" unless defined $pid;

if ($pid == 0) {
  # child
  open STDERR, ">", "/dev/null" or die "$0: open: $!";
  exec "./prog.r", "foo & bar"  or exit 1;  # STDERR silent now
}

while (<$command_out>) {
  print "got: $_";
}

close $command_out or warn "$0: prog.r exited " . ($? >> 8);

打开"-|"上的句柄,分叉一个孩子并将孩子的标准输出连接到该句柄。与fork类似,它向子节点返回0,向父节点返回非零进程标识符,或者在失败时返回未定义的值。

在孩子中,我们首先将STDERR重定向到/dev/null,然后使用exec将孩子替换为我们的R计划。请注意,我们以列表形式传递命令以绕过shell:

  

如果LIST中有多个参数,或者LIST是一个具有多个值的数组,则使用LIST中的参数调用execvp(3)

因为我们不能再看到标准错误了,所以明确close $command_out检查孩子是否幸福地运行是很重要的。否则,你会得到一个令人费解的无声失败。

示例运行:

$ ./prog.pl 
got: foo & bar

STDERR合并到STDOUT

要查看句柄上的标准错误,请使用2>&1例如

open my $command_out, "-|", "./prog.r foo 2>&1" or die;

安全管道打开,dup标准输出上的标准错误:

if ($pid == 0) {
  # child
  open STDERR, ">&", \*STDOUT  or die "$0: open: $!";
  exec "./prog.r", "foo & bar" or die "$0: exec: $!";
}

open文档涵盖了这一点:

  

在Bourne shell传统中,您还可以指定以>&开头的EXPR,在这种情况下,字符串的其余部分将被解释为文件句柄(或文件描述符,如果是数字)的名称欺骗(dup(2))并打开。

即使您可以通过这种方式查看标准错误,但仍然可以使用close检查孩子的退出状态。

现在所有到达$command_out

got: Loading required package: Hmisc
got: Loading required package: methods
got: Loading required package: survival
got: Loading required package: stats
got: Loading required package: utils
got: Loading required package: graphics
got: Loading required package: splines
got: 
got: Attaching package: 'Hmisc'
got: 
got: 
got:    The following object(s) are masked from package:survival :
got: 
got:     untangle.specials 
got: 
got: 
got:    The following object(s) are masked from package:base :
got: 
got:     format.pval,
got:     round.POSIXt,
got:     trunc.POSIXt,
got:     units 
got: 
got: foo & bar

答案 1 :(得分:4)

一种简单的方法是将2&gt;&amp; 1附加到$ command_string,但这是否有效取决于你的shell。 (例如,解释$ command_string的shell)

答案 2 :(得分:3)

使用IPC::Run分别捕获STDOUT和STDERR。 pump函数可以即时为您提供输出。

答案 3 :(得分:1)

也许你可以将STDERR重定向到整个程序的STDOUT?

*STDERR = *STDOUT;
open( my $command.... );

答案 4 :(得分:0)

自行分叉并执行:

pipe R, W; # note: R and W have CLOEXEC set
if (fork == 0) {
    # child #
    open STDERR, '>&W';
    exec qw/ls -lrtac/, $ARGV[0];
    die 'not reached';
}
# parent #
close W;
while (<R>) { do_something }
close R;
wait;

}