重定向将由父进程启动的所有子进程的STDOUT和STDERR

时间:2019-11-15 12:29:28

标签: windows perl

在Perl(使用5.30.0版)中,是否可以以编程方式(即在我的Perl代码中)更改某些设置,从而导致STDOUT和STDERR重定向到我的Perl进程将要使用的任何子进程的文本文件从那点开始?这里的困难在于,我无法控制实际上启动那些子进程的代码(如果我这样做了,那将是微不足道的)。我的希望是,可以在Perl进程中设置某种IO标志,以使所有新启动的子进程将其STDOUT / STDERR通道重定向到我选择的目的地(除非system()/ exec() /用于启动它们的任何调用都明确提供了另一个STDOUT / STDERR目标)。

以下是我需要的(高度简化)用例。我编写了一个通用库函数,该函数执行调用方指定的任务,除其他外,将用户提供的任务生成的所有STDOUT / STDERR输出存储在文本文件中。以Perl函数参考的形式提供任务。到目前为止,我仅设法重定向了由我的Perl进程生成的STDOUT / STDERR输出:

    local *STDOUT;
    local *STDERR;

    open( STDOUT, '>', $buildLogFilePath ) or die sprintf(
        "ERROR: [%s] Failed to redirect STDOUT to \"%s\"!\n",
        $messageSubject,
        $buildLogFilePath
    );

    open ( STDERR, '>&', STDOUT ) or die sprintf(
        "ERROR: [%s] Failed to redirect STDERR to \"%s\"!\n",
        $messageSubject,
        $buildLogFilePath
    );

只要用户提供的功能不启动任何子进程,我的功能就可以按需运行,但是一旦启动子进程,该进程的输出将直接进入默认的STDOUT / STDERR通道,有效地破坏了我的功能。

编辑:我最终使用了以下 ikegami 描述的STDOUT / STDERR指向技巧。为了更轻松地重用此技巧,我将代码包装在一个小的实用程序类中,该类将在构造函数中重定向STDOUT / STDERR,然后在析构函数中将它们还原为原始值:

package TemporaryOutputRedirector;

use strict;
use warnings FATAL => 'all';

use Carp::Assert;

sub new
{
    my ( $class, $newDest ) = @_;

    open( my $savedStdout, '>&', STDOUT ) or die sprintf(
        "ERROR: Failed to save original STDOUT in process %i!\n",
        $$
    );

    open( STDOUT, '>', $newDest ) or die sprintf(
        "ERROR: Failed to redirect STDOUT to \"%s\" in child process %i!\n",
        $newDest,
        $$
    );

    open( my $savedStderr, '>&', STDERR ) or die sprintf(
        "ERROR: Failed to save original STDERR in process %i!\n",
        $$
    );

    open ( STDERR, '>&', STDOUT ) or die sprintf(
        "ERROR: Failed to redirect STDERR in child process %i!\n",
        $$
    );

    my %memberData = (
        'newDest'     => $newDest,
        'savedStdout' => $savedStdout,
        'savedStderr' => $savedStderr
    );

    return bless \%memberData, ref $class || $class;
}

sub DESTROY
{
    my ( $self ) = @_;

    my $savedStdout = $$self{ 'savedStdout' };
    assert( defined( $savedStdout ) );

    open( STDOUT, '>&', $savedStdout ) or warn sprintf(
        "ERROR: Failed to restore original STDOUT in process %i!\n",
        $$
    );

    my $savedStderr = $$self{ 'savedStderr' };
    assert( defined( $savedStderr ) );

    open( STDERR, '>&', $savedStderr ) or warn sprintf(
        "ERROR: Failed to restore original STDERR in process %i!\n",
        $$
    );
}

1;

这样,只需创建一个TemporaryOutputRedirector的实例,执行应重定向其输出的代码,然后让TemporaryOutputRedirector超出范围并使其析构函数还原原始STDOUT / STDERR通道。

1 个答案:

答案 0 :(得分:2)

打开文件句柄时,它使用下一个可用的文件描述符。 STDIN,STDOUT和STDERR通常分别与fd 0、1和2关联。如果进程中没有其他打开的句柄,则open创建的下一个句柄将使用文件描述符3。

如果将fd 3与STDOUT关联,许多事情将继续起作用。这是因为Perl代码通常处理Perl文件句柄而不是文件描述符。例如,print LIST实际上是print { select() } LIST,默认情况下与print STDOUT LIST相同。因此,您的更改通常在Perl中有效。

但是,当您执行程序时,它所获得的只是文件描述符。它得到fd 0、1和2。它甚至可能得到fd 3,但它并不在乎。它将输出到fd 1。


一个简单的解决方案是删除local *STDOUT; local *STDERR;

*STDOUT是一个glob,是一个包含*STDOUT{IO}(所涉及的句柄)的结构。

通过使用local *STDOUT,您将用一个空的glob替换该glob。原始文件不会被销毁-local超出范围时将被恢复-因此与现在匿名的glob相关联的Perl文件句柄将不会被关闭,因此与该句柄相关联的fd会被赢得不会关闭,因此后续的open无法重用该fd。

如果您避免执行local *STDOUT,则意味着您正在将打开的句柄传递给openopen在这种情况下的行为特别:它将“重新打开”已经与Perl句柄关联的fd,而不是创建新的fd。

$ perl -e'
   open( local *STDOUT, ">", "a" ) or die;
   open( local *STDERR, ">&", STDOUT ) or die;
   print(fileno(STDOUT), "\n");
   system("echo foo");
'
foo

$ cat a
3

$ perl -e'
   open( STDOUT, ">", "a" ) or die;
   open( STDERR, ">&", STDOUT ) or die;
   print(fileno(STDOUT), "\n");
   system("echo foo");
'

$ cat a
1
foo

如果您希望重定向是临时的。您必须使用文件描述符。

$ perl -e'
   open( local *SAVED_STDOUT, ">&", STDOUT) or die;
   open( STDOUT, ">", "a" ) or die;
   print(fileno(STDOUT), "\n");
   system("echo foo");

   open( STDOUT, ">&", SAVED_STDOUT) or die;
   print(fileno(STDOUT), "\n");
   system("echo bar");
'
1
bar

$ cat a
1
foo