在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通道。
答案 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
,则意味着您正在将打开的句柄传递给open
。 open
在这种情况下的行为特别:它将“重新打开”已经与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