簡體   English   中英

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

[英]Perl: How to pass IPC::Open3 redirected STDOUT/STDERR fhs

我正在嘗試捕獲輸出,我的perl代碼從print和類似語句以及外部命令生成。

由於設計限制,我無法使用Capture :: Tiny等解決方案。 我需要在生成后立即將輸出轉發到緩沖區變量,我需要能夠區分STDOUTSTDERR 理想情況下,除了能夠捕獲STDOUTSTDERR而不是打印它們之外,外部命令的解決方案基本上就像系統一樣工作。

我的代碼應該是:

  1. 保存舊的STDOUT / STDERR文件句柄。
  2. STDERRSTDOUT創建一個新的。
  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

我沒有為您提供解決方案,但我可以就您所看到的行為提供一些解釋。

首先,當文件句柄是變量時, 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 ):

這解釋了為什么當你使用\\$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);

這遠非我原來想要的,但似乎至少起作用。

我遇到的問題是:

  1. 我需要一個匿名文件句柄在硬盤上創建雜亂。
  2. 出於某種原因,我需要手動修復編碼。

非常感謝那些花時間幫助我的人們。

您是否有理由需要使用父級的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM