Perl:如何通过IPC :: Open3重定向的STDOUT / STDERR fhs

时间:2019-03-25 12:50:06

标签: linux perl

我正在尝试捕获我的 perl 代码从print和类似语句以及外部命令生成的输出。

由于设计上的限制,我不能使用Capture :: Tiny之类的解决方案。我需要在生成输出后立即将其转发到缓冲区变量,并且需要能够区分 STDOUT STDERR 。理想情况下,针对外部命令的解决方案基本上可以像系统一样工作,除了能够捕获 STDOUT STDERR 而不是打印它们。

我的代码应该:

  1. 保存旧的 STDOUT / STDERR 文件句柄。
  2. STDERR STDOUT 创建新的文件。
  3. 将所有输出重定向到此位置。
  4. 打印一些内容。
  5. 还原旧文件句柄。
  6. 对捕获的输出执行某些操作,例如打印出来。

但是,我无法捕获外部命令生成的输出。我无法使用IPC::Run3IPC::Open3来做到这一点。

#!/usr/bin/perl -CSDAL
use warnings;
use strict;
use IPC::Open3;
#use IPC::Run3;

# Save old filehandles
open(my $oldout, ">&STDOUT") or die "Can't dup STDOUT: $!";
open(my $olderr, ">&STDERR") or die "Can't dup STDERR: $!";

my $buffer = "";

close(STDOUT);
close(STDERR);

open(STDOUT, '>', \$buffer) or die "Can't redirect STDOUT: $!";
*STDERR = *STDOUT; # In this example STDOUT and STDERR are printed to the same buffer.

print "1: Test\n";
#run3 ["date"], undef, \*STDOUT, \*STDERR; # This doesn't work as expected
my $pid = open3("<&STDIN", ">&STDOUT", ">&STDERR", "date");
waitpid($pid,0); # Nor does this.

print STDERR "2: Test\n";

open(STDOUT, ">&", $oldout) or die "Can't dup \$oldout: $!";
open(STDERR, ">&", $olderr) or die "Can't dup \$olderr: $!";

print "Restored!\n";
print $buffer;

预期结果:

Restored!
1: Test
Mo 25. Mär 13:44:53 CET 2019
2: Test

实际结果:

Restored!
1: Test
2: Test

4 个答案:

答案 0 :(得分:2)

我没有为您提供解决方案,但是我可以提供一些有关您所看到的行为的解释。

首先,当文件句柄为变量时,IPC::Open3不起作用;有关更多说明,请参见this question

现在,IPC::Run3为什么不起作用?首先,请注意,如果不重定向STDERR并运行

run3 ["date"], undef, \$buffer, { append_stdout => 1 };

代替

run3 ["date"], undef, \*STDOUT;

然后按预期方式工作。 (您需要添加{ append_stdout => 1 },否则您先前向$buffer的输出将被覆盖)

要了解发生了什么情况,请在

之后
open(STDOUT, '>', \$buffer) or die "Can't redirect STDOUT: $!";

添加

print STDERR ref(\$buffer), "\n"
print STDERR ref(\*STDOUT), "\n"

将打印哪个

SCALAR
GLOB

这正是IPC::Run3::run3会做的,以了解如何处理您给它的“ stdout”(请参阅​​来源:_fh_for_child_output,由run3调用):

其中解释了为什么当您使用run3调用\$buffer而不是\*STDOUT时起作用。


当也重定向STDERR并调用

run3 ["date"], undef, \$buffer, \$buffer, { append_stdout => 1, append_stderr => 1 };

,事情开始显得怪异。我不了解发生了什么,但我将在这里分享我发现的内容,希望有人能理解。

我修改了IPC::Run3的来源并添加了

open my $FP, '>', 'logs.txt' or die "Can't open: $!";
在子run3开头的

。运行时,我只会看到

Restored!
1: Test

在STDOUT(我的终端)上,但是logs.txt包含日期(Mon Mar 25 17:49:44 CET 2019行中的内容)。

稍作投资后,发现fileno $FP返回1(除非我误解为通常为STDOUT(但您将其关闭,所以对于它的描述符可以被重用)),然后fileno STDOUT返回2(这可能取决于您的Perl版本和其他打开的文件句柄)。 似乎正在发生的事情是,system假设STDOUT是文件描述符1,因此打印到$FP而不是{{1} }(我只是在猜测)。

如果您了解发生了什么,请随时评论/编辑。

答案 1 :(得分:1)

我最终得到了以下代码:

#!/usr/bin/perl -CSDAL
use warnings;
use strict;
use IPC::Run3;
use IO::Scalar;
use Encode;
use utf8;

# Save old filehandles
open(my $oldout, ">&STDOUT") or die "Can't dup STDOUT: $!";
open(my $olderr, ">&STDERR") or die "Can't dup STDERR: $!";

open(my $FH, "+>>:utf8", undef) or die $!;
$FH->autoflush;

close(STDOUT);
close(STDERR);

open(STDOUT, '>&', $FH) or die "Can't redirect STDOUT: $!";
open(STDERR, '>&', $FH) or die "Can't redirect STDOUT: $!";

print "1: Test\n";

run3 ["/bin/date"], undef, $FH, $FH, { append_stdout => 1, append_stderr => 1 };

print STDERR "2: Test\n";

open(STDOUT, ">&", $oldout) or die "Can't dup \$oldout: $!";
open(STDERR, ">&", $olderr) or die "Can't dup \$olderr: $!";

print "Restored!\n";
seek($FH, 0, 0);
while(<$FH>)
{
  # No idea why this is even required
  print Encode::decode_utf8($_);
}
close($FH);

这与我最初想要的相去甚远,但似乎至少可以正常工作。

与此有关的问题是:

  1. 我需要一个匿名文件句柄在硬盘上造成混乱。
  2. 出于某种原因,我需要手动修复编码。

非常感谢那些在这里帮助我的人。

答案 2 :(得分:0)

您是否有理由需要使用父母的STDOUT和STDERR? IPC :: Open3能够轻松地将孩子的STDOUT和STDERR重定向到父母中不相关的句柄,您可以从中读取这些句柄。

use strict;
use warnings;
use IPC::Open3;

my $pid = open3 undef, my $outerr, undef, 'date';
my $output = do { local $/; readline $outerr };
waitpid $pid, 0;
my $exit = $? >> 8;

这将一起读取STDOUT和STDERR,如果要分别读取它们,则需要传递my $stderr = Symbol::gensym作为第三个参数(如IPC :: Open3文档中所示),并使用非阻塞循环避免在读取两个手柄时出现死锁。 IO::Async::Process或类似的名称可以为您完全自动化,但是如果您只需要将输出存储在标量变量中,则IPC :: Run3提供了一个更简单的解决方案。 IPC :: Run3和Capture :: Tiny也可以很容易地fatpacked部署在脚本中。

答案 3 :(得分:-1)

这还不是答案,但是似乎open3要求STDOUT在您调用open3时是常规的tty文件句柄,例如:

use feature qw(say);
use strict;
use warnings;

use IPC::Open3;
use Symbol 'gensym';
{
    local *STDOUT;  # <-- if you comment out this line open3 works as expected
    my ($chld_in, $chld_out);
    my $chld_err = gensym;
    my $pid;
    eval {
        $pid = open3($chld_in, $chld_out, $chld_err, "date");
    };
    if ( $@ ) {
        say "IPC::Open::open3 failed: '$@'";
    }
    print "-> $_" for <$chld_out>;
    waitpid $pid, 0;
   # say "Cannot print to invalid handle..";
}
say "ok";

输出

ma. 25. mars 16:00:01 +0100 2019
ok

请注意,该行开头的箭头->丢失了, 因此在这种情况下,无法从$chld_out中读取任何内容。但是,如果我注释掉这一行:

local *STDOUT;

输出为:

-> ma. 25. mars 16:01:10 +0100 2019
ok