Прямой STDERR при открытии трубы в perl - PullRequest
2 голосов
/ 15 августа 2010

Я использую open( my $command_out, "-|", $command_string ) для выполнения команды и обработки ее вывода на лету (не нужно ждать, пока команда завершится первой, как в system()).

Я заметил, что когда я вызываю некоторые R-сценарии таким образом, некоторые из R-сообщений выводятся на экран (например, Loading required package: ...). Я предполагаю, что это потому, что R отправляет этот вывод в stderr (хотя на самом деле это не ошибки).

Можно ли направить этот вывод слишком на $command_out при open() -ing, чтобы экран оставался чистым?

Ответы [ 5 ]

10 голосов
/ 15 августа 2010

Скажите, что ваша R-программа

#! /usr/bin/env r

require(Hmisc)

cat(argv[1], "\n")

, который оказывается удивительно болтливым:

$ ./prog.r foo
Loading required package: Hmisc
Loading required package: methods
Loading required package: survival
Loading required package: stats
Loading required package: utils
Loading required package: graphics
Loading required package: splines

Attaching package: 'Hmisc'


    The following object(s) are masked from package:survival :

     untangle.specials 


    The following object(s) are masked from package:base :

     format.pval,
     round.POSIXt,
     trunc.POSIXt,
     units 

foo

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

Отказ от стандартной ошибки

Один из способов отбросить стандартную ошибку - использовать перенаправление оболочки 2>/dev/null. Это общий механизм, а 2 - это дескриптор файла стандартной ошибки . Например:

#! /usr/bin/perl

use warnings;
use strict;

open my $command_out, "-|", "./prog.r foo 2>/dev/null"
  or die "$0: could not start prog.r";

while (<$command_out>) {
  print "got: $_";
}

Оболочка также будет обрабатывать backtick или qx// выражения

#! /usr/bin/perl

use warnings;
use strict;

(my $command_out = `./prog.r foo 2>/dev/null`) =~ s/^/got: /mg;
print $command_out;

и скалярная команда, переданная system

#! /usr/bin/perl

use warnings;
use strict;

system("./prog.r foo 2>/dev/null") == 0
  or warn "$0: prog.r exited " . ($? >>8);

Для всех этих выводов

$ ./prog.pl 
got: foo

Но иногда вы не хотите, чтобы оболочка надевала свои грязные руки на вашу команду. Может быть, он содержит метасимволы или кавычки, которые вы не хотите использовать для экранирования. Это когда безопасный открытый трубопровод полезен:

#! /usr/bin/perl

use warnings;
use strict;

my $pid = open my $command_out, "-|";
die "$0: fork: $!" unless defined $pid;

if ($pid == 0) {
  # child
  open STDERR, ">", "/dev/null" or die "$0: open: $!";
  exec "./prog.r", "foo & bar"  or exit 1;  # STDERR silent now
}

while (<$command_out>) {
  print "got: $_";
}

close $command_out or warn "$0: prog.r exited " . ($? >> 8);

Открытие дескриптора на "-|" разветвляет дочернего элемента и соединяет стандартный выход дочернего элемента с этим дескриптором. Как и fork, он возвращает 0 дочернему элементу, ненулевой идентификатор процесса для родительского элемента или неопределенное значение при ошибке.

В дочернем процессе мы сначала перенаправляем STDERR на /dev/null, а затем используем exec для замены дочернего элемента нашей программой R. Обратите внимание, что мы передали команду в виде списка, чтобы обойти оболочку:

Если в LIST имеется более одного аргумента или если LIST является массивом с более чем одним значением, он вызывает execvp (3) с аргументами в LIST.

Поскольку мы больше не видим стандартную ошибку, важно явно close $command_out проверить, что ребенок бежал счастливо. В противном случае вы получите загадочный тихий сбой.

Пример прогона:

$ ./prog.pl 
got: foo & bar

Объединить STDERR в STDOUT

Чтобы увидеть стандартную ошибку на вашем дескрипторе, используйте 2>&1 вместо , например, ,

open my $command_out, "-|", "./prog.r foo 2>&1" or die;

При безопасном открытии трубы dup стандартная ошибка на стандартном выходе:

if ($pid == 0) {
  # child
  open STDERR, ">&", \*STDOUT  or die "$0: open: $!";
  exec "./prog.r", "foo & bar" or die "$0: exec: $!";
}

Документация open охватывает это:

Вы также можете, в традиции оболочки Bourne, указать EXPR, начинающийся с >&, и в этом случае остальная часть строки интерпретируется как имя дескриптора файла (или дескриптора файла, если числовой), который должен быть дублирован ( как dup (2) ) и открыто.

Даже если вы можете увидеть стандартную ошибку таким образом, все же будет хорошей идеей проверить состояние выхода ребенка с помощью close.

Теперь все приходит $command_out:

got: Loading required package: Hmisc
got: Loading required package: methods
got: Loading required package: survival
got: Loading required package: stats
got: Loading required package: utils
got: Loading required package: graphics
got: Loading required package: splines
got: 
got: Attaching package: 'Hmisc'
got: 
got: 
got:    The following object(s) are masked from package:survival :
got: 
got:     untangle.specials 
got: 
got: 
got:    The following object(s) are masked from package:base :
got: 
got:     format.pval,
got:     round.POSIXt,
got:     trunc.POSIXt,
got:     units 
got: 
got: foo & bar
4 голосов
/ 15 августа 2010

Самый простой способ - добавить 2> & 1 к $ command_string, но будет ли это работать, зависит от вашей оболочки. (например, оболочка, которая интерпретирует $ command_string)

3 голосов
/ 15 августа 2010

Используйте IPC::Run для захвата STDOUT и STDERR по отдельности.Функция pump дает вам вывод на лету.

1 голос
/ 15 августа 2010

Может быть, вы могли бы перенаправить STDERR в STDOUT для всей программы?

*STDERR = *STDOUT;
open( my $command.... );
0 голосов
/ 16 июня 2019

Сделай себе и исполняй себя:

pipe R, W; # note: R and W have CLOEXEC set
if (fork == 0) {
    # child #
    open STDERR, '>&W';
    exec qw/ls -lrtac/, $ARGV[0];
    die 'not reached';
}
# parent #
close W;
while (<R>) { do_something }
close R;
wait;

}

...