首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Perl:如何传递IPC::Open3重定向STDOUT/STDERR fhs

Perl:如何传递IPC::Open3重定向STDOUT/STDERR fhs
EN

Stack Overflow用户
提问于 2019-03-25 12:50:05
回答 4查看 967关注 0票数 5

我试图捕获我的perl代码从打印语句、类似语句和外部命令生成的输出。

由于设计上的限制,我不能使用解决方案,比如Capture::Tiny。生成缓冲区变量后,我需要将输出转发到缓冲区变量,并且需要能够区分、STDOUT、STDERR。理想情况下,外部命令的解决方案除了能够捕获、STDOUT、STDERR之外,在本质上就像系统一样工作。

我的代码应该是:

  1. 保存旧的STDOUT/STDERR文件句柄。
  2. STDERRSTDOUT创建一个新的STDERR。
  3. 把所有的输出重定向到这个地方。
  4. 印几样东西。
  5. 还原旧文件句柄。
  6. 对捕获的输出做一些事情,例如打印它。

但是,我无法捕获从外部命令生成的输出。我既不能用IPC::Run3,也不能用IPC::Open3

代码语言:javascript
复制
#!/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;

预期结果:

代码语言:javascript
复制
Restored!
1: Test
Mo 25. Mär 13:44:53 CET 2019
2: Test

实际结果:

代码语言:javascript
复制
Restored!
1: Test
2: Test
EN

回答 4

Stack Overflow用户

发布于 2019-03-25 15:10:45

我没有办法给你提供解决方案,但是我可以为你看到的行为提供一些解释。

首先,当文件句柄是变量时,IPC::Open3不应该工作;有关更多解释,请参见这个问题

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

代码语言:javascript
复制
run3 ["date"], undef, \$buffer, { append_stdout => 1 };

而不是

代码语言:javascript
复制
run3 ["date"], undef, \*STDOUT;

那么它就会像预期的那样工作。(您需要添加{ append_stdout => 1 },否则以前的$buffer输出将被覆盖)

了解正在发生的事情,在您的程序中,在

代码语言:javascript
复制
open(STDOUT, '>', \$buffer) or die "Can't redirect STDOUT: $!";

添加

代码语言:javascript
复制
print STDERR ref(\$buffer), "\n"
print STDERR ref(\*STDOUT), "\n"

它将打印

代码语言:javascript
复制
SCALAR
GLOB

这正是IPC::Run3::run3要知道如何处理"stdout“所做的事情(请参阅源代码:_fh_for_child_output,由run3调用):

这就解释了为什么当您用run3调用\$buffer时,它是有效的,但对\*STDOUT却不起作用。

在重定向STDERR和调用

代码语言:javascript
复制
run3 ["date"], undef, \$buffer, \$buffer, { append_stdout => 1, append_stderr => 1 };

事情开始变得怪怪的。我不明白发生了什么,但我会在这里分享我所发现的,希望有人能理解它。

我修改了IPC::Run3的源代码,并添加了

代码语言:javascript
复制
open my $FP, '>', 'logs.txt' or die "Can't open: $!";

在子run3的开头。在跑步的时候,我只看到

代码语言:javascript
复制
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 (不过,我只是猜测)。

如果您了解正在发生的事情,请随时评论/编辑。

票数 4
EN

Stack Overflow用户

发布于 2019-03-26 10:06:44

最后,我得到了以下代码:

代码语言:javascript
复制
#!/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
EN

Stack Overflow用户

发布于 2019-03-25 16:33:50

您是否需要使用父母的STDOUT和STDERR?IPC::Open3可以轻松地将子程序的STDOUT和STDERR重定向到您可以从中读取的父进程中的无关句柄。

代码语言:javascript
复制
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也可以很容易地成为油腻,以便在脚本中进行部署。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/55338223

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档