我试图捕获我的perl代码从打印语句、类似语句和外部命令生成的输出。
由于设计上的限制,我不能使用解决方案,比如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发布于 2019-03-25 15:10:45
我没有办法给你提供解决方案,但是我可以为你看到的行为提供一些解释。
首先,当文件句柄是变量时,IPC::Open3不应该工作;有关更多解释,请参见这个问题。
现在,为什么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的函数)。GLOB (或绑定到IO::Handle)时,直接使用该文件句柄(即这个代码行)。这就解释了为什么当您用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而不是STDOUT (不过,我只是猜测)。
如果您了解正在发生的事情,请随时评论/编辑。
发布于 2019-03-26 10:06:44
最后,我得到了以下代码:
#!/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);这与我最初想要的相去甚远,但似乎至少在起作用。
我在这方面的问题是:
非常感谢那些把他们的时间奉献给我的人。
发布于 2019-03-25 16:33:50
您是否需要使用父母的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::Open3docs中所示),并且在读取两个句柄时使用非阻塞循环以避免死锁。IO::异步::Process或类似的程序可以为您完全自动化,但是IPC::Run3提供了一个简单得多的解决方案,如果您只需要将输出存储在标量变量中。IPC::Run3和Capture::Tiny也可以很容易地成为油腻,以便在脚本中进行部署。
https://stackoverflow.com/questions/55338223
复制相似问题