Perl: как передать IPC :: Open3 перенаправлен STDOUT / STDERR fhs - PullRequest
5 голосов
/ 25 марта 2019

Я пытаюсь получить выходные данные, которые мой код perl генерирует как из операторов print, так и из аналогичных операторов и внешних команд.

Из-за конструктивных ограничений я не могу использовать такие решения, как Capture :: Tiny. Мне нужно перенаправить вывод в буферную переменную, как только она будет сгенерирована, и я должен иметь возможность различать STDOUT и STDERR . В идеале решение для внешних команд, по сути, должно работать так же, как система, за исключением возможности захвата STDOUT и STDERR вместо их печати. ​​

Мой код должен:

  1. Сохранить старые STDOUT / STDERR файловые дескрипторы.
  2. Создайте новые для STDERR и STDOUT .
  3. Перенаправить весь вывод в это место.
  4. Распечатать пару вещей.
  5. Восстановить старые файловые дескрипторы.
  6. Сделайте что-нибудь с захваченным выводом, например, распечатай.

Однако я не могу захватить вывод, сгенерированный из внешних команд. Я не могу сделать это ни с 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

Ответы [ 4 ]

2 голосов
/ 25 марта 2019

У меня нет решения, чтобы предложить вам, однако я могу дать некоторые объяснения относительно поведения, которое вы видите.

Во-первых, 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 (хотя я просто предполагаю).

Пожалуйста, не стесняйтесь комментировать / редактировать, если вы понимаете, что происходит.

1 голос
/ 26 марта 2019

Я получил следующий код:

#!/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. По какой-то причине мне нужно исправить кодировку вручную.

Большое спасибо людям, которые посвятили мне свое время помогать мне здесь.

0 голосов
/ 25 марта 2019

Есть ли причина, по которой вам нужно использовать 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 также могут быть легко упакованы для развертывания в сценариях.

0 голосов
/ 25 марта 2019

Это еще не ответ, но похоже, что open3 требует, чтобы STDOUT был обычным дескриптором tty файла во время вызова open3, например:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...