在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通道破坏了我的功能。
打开文件句柄时,它使用下一个可用的文件描述符。 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
是一个小球,一个包含*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