我正在尝试捕获我的 perl 代码从print和类似语句以及外部命令生成的输出。
由于设计上的限制,我不能使用Capture :: Tiny之类的解决方案。我需要在生成输出后立即将其转发到缓冲区变量,并且需要能够区分 STDOUT 和 STDERR 。理想情况下,针对外部命令的解决方案基本上可以像系统一样工作,除了能够捕获 STDOUT 和 STDERR 而不是打印它们。
我的代码应该:
但是,我无法捕获外部命令生成的输出。我无法使用IPC::Run3
或IPC::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
答案 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
调用):>
如果是标量,则使用一个临时文件(对应的行是$fh = $fh_cache{$what} ||= tempfile
,其中tempfile
是File::Temp
中的函数。
另一方面,当stdout是GLOB
(或与IO::Handle
绑定)时,将直接使用该文件句柄(即line of code)。
其中解释了为什么当您使用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);
这与我最初想要的相去甚远,但似乎至少可以正常工作。
与此有关的问题是:
非常感谢那些在这里帮助我的人。
答案 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