gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-05 03:56:56 29 4
gpt4 key购买 nike

在 Perl 中(使用版本 5.30.0),是否可以通过编程方式(即在我的 Perl 代码中)更改一些设置,这会导致 STDOUT 和 STDERR 被重定向到我的 Perl 进程将要处理的任何子进程的文本文件从那一点开始?这里的困难是我不控制实际启动那些子进程的代码(如果我控制了,这将是非常微不足道的)。我希望我可以在我的 Perl 进程中设置某种 IO 标志,这将导致所有新启动的子进程将它们的 STDOUT/STDERR channel 重定向到我选择的目的地(除非 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 channel ,有效地破坏了我的功能。

编辑:我最终使用了下面 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 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 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 channel 。

最佳答案

当您打开文件句柄时,它会使用下一个可用的文件描述符。 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;本地 *STDERR;.

*STDOUT 是一个 glob,一个包含 *STDOUT{IO} 的结构,即有问题的句柄。

通过使用 local *STDOUT,您可以将 glob 替换为空的。原始的不会被破坏——当 local 超出范围时它会被恢复——所以与现在匿名的 glob 关联的 Perl 文件句柄不会被关闭,所以与该句柄不会关闭,因此后续的 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

关于windows - 为将由父进程启动的所有子进程重定向 STDOUT 和 STDERR,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58877124/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com