[英]Perl: How to pass IPC::Open3 redirected STDOUT/STDERR fhs
我正在嘗試捕獲輸出,我的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
我沒有為您提供解決方案,但我可以就您所看到的行為提供一些解釋。
首先,當文件句柄是變量時, 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
會做知道與“標准輸出”你給它做(見來源: _fh_for_child_output
,這是由所謂的run3
):
如果它是標量,則使用臨時文件(對應的行是$fh = $fh_cache{$what} ||= tempfile
,其中tempfile
是File::Temp
的函數。
另一方面,當stdout是GLOB
(或綁定到IO::Handle
)時,直接使用該文件句柄(即這行代碼 )。
這解釋了為什么當你使用\\$buffer
調用run3
時它可以工作,但不能用\\*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: $!";
在sub 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
(我只是猜測)。
如果您了解正在發生的事情,請隨時評論/編輯。
我最終得到以下代碼:
#!/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);
這遠非我原來想要的,但似乎至少起作用。
我遇到的問題是:
非常感謝那些花時間幫助我的人們。
您是否有理由需要使用父級的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以便在腳本中進行部署。
這還不是答案,但似乎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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.