在 perl 中打开管道时直接 STDERR

发布于 2024-09-14 13:14:57 字数 322 浏览 7 评论 0原文

我正在使用 open( my $command_out, "-|", $command_string ) 来执行命令并即时处理其输出(不必先等待命令完成,如 <代码>系统())。

我注意到,当我以这种方式调用一些 R 脚本时,一些 R 消息会打印到屏幕上(例如 正在加载所需的包:...)。我猜这是因为 R 将此输出发送到 stderr (?尽管这些并不是真正的错误)。

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

I am using open( my $command_out, "-|", $command_string ) to execute a command and process its output on the fly (not having to wait for the command to finish first, as in system()).

I noticed that when I call some R scripts this way, some of R messages are printed to the screen (e.g. Loading required package: ...). I guess this is because R sends this output to stderr (? although these are not really errors).

Is it possible to direct this output too to $command_out when open()-ing so the screen will remain clean?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

梦中楼上月下 2024-09-21 13:14:57

假设您的 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

从这里,您可以丢弃标准错误或将其与另一个流合并。

丢弃标准错误

丢弃标准错误的一种方法是使用 shell 重定向 2>/dev/null。这是一个通用机制,2 是标准错误的文件描述符。例如:

#! /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 还将处理 反引号或 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 元字符或引号。这是安全管道打开有用的时候:

#! /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 是一个具有多个值的数组,则调用 execvp(3) 以及 LIST 中的参数。

因为我们无法再看到标准错误,所以显式关闭 $command_out 以检查子进程是否运行愉快非常重要。否则,您会遇到令人费解的无声失败。

示例运行:

$ ./prog.pl 
got: foo & bar

STDERR 合并到 STDOUT

要查看句柄上的标准错误,请改用 2>&1eg< /em>,

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

Say your R program is

#! /usr/bin/env r

require(Hmisc)

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

which turns out to be surprisingly chatty:

$ ./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

From here, you could discard the standard error or merge it with another stream.

Discarding the standard error

One way to discard the standard error is to use a shell redirect of 2>/dev/null. This is a general mechanism, and 2 is the standard error's file descriptor. For example:

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

The shell will also process backtick or qx// expressions

#! /usr/bin/perl

use warnings;
use strict;

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

and a scalar command passed to system

#! /usr/bin/perl

use warnings;
use strict;

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

For all of these, the output is

$ ./prog.pl 
got: foo

But sometimes you don't want the shell putting its grubby mitts on your command. Maybe it contains shell metacharacters or quotes that you don't want to deal with escaping. This is when a safe pipe-open is useful:

#! /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);

Opening a handle on "-|" forks a child and connects the child's standard output to that handle. Like fork, it returns 0 to the child, a non-zero process identifier to the parent, or the undefined value on failure.

In the child, we first redirect STDERR to /dev/null and then use exec to replace the child with our R program. Note that we passed the command in list form to bypass the shell:

If there is more than one argument in LIST, or if LIST is an array with more than one value, calls execvp(3) with the arguments in LIST.

Because we can't see the standard error any longer, it's important to explicitly close $command_out to check that the child ran happily. Otherwise, you'd get a puzzling silent failure.

Sample run:

$ ./prog.pl 
got: foo & bar

Merge STDERR into STDOUT

To see the standard error on your handle, use 2>&1 instead, e.g.,

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

With a safe pipe-open, dup the standard error onto the standard output:

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

The open documentation covers this:

You may also, in the Bourne shell tradition, specify an EXPR beginning with >&, in which case the rest of the string is interpreted as the name of a filehandle (or file descriptor, if numeric) to be duped (as dup(2)) and opened.

Even though you can see the standard error this way, it's still a good idea to check the child's exit status with close.

Now everything arrives over $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
番薯 2024-09-21 13:14:57

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

An easy way is to append 2>&1 to $command_string, but whether or not this works will depend on your shell. (eg, the shell that interprets $command_string)

烟若柳尘 2024-09-21 13:14:57

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

Use IPC::Run to capture STDOUT and STDERR separately. The pump function gives you the output on the fly.

阳光下的泡沫是彩色的 2024-09-21 13:14:57

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

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

Maybe you could redirect STDERR to STDOUT for the whole program?

*STDERR = *STDOUT;
open( my $command.... );
逆蝶 2024-09-21 13:14:57

做叉子和执行自己:

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;

}

Do fork & exec yourself:

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;

}

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文