在子进程中重定向 STDOUT
Posted
技术标签:
【中文标题】在子进程中重定向 STDOUT【英文标题】:Redirecting STDOUT in child process 【发布时间】:2012-11-01 05:52:00 【问题描述】:有一个通过 fork 产生多个子进程的父进程。我希望父进程和子进程的日志文件是分开的。问题是子进程 STDOUT 被重定向到父日志文件以及子日志文件。不确定我需要更改什么以避免子进程日志消息进入父日志文件。我也不明白在下面的 setEnvironment 函数中创建 OUT 和 ERR 文件句柄的目的。这是一个现有的代码,所以我保持原样。在父进程和子进程中,我将变量 $g_LOGFILE 设置为包含不同的文件名,以便创建单独的日志文件。我也在父进程和子进程中调用 setEnvironment 函数。我尝试在子进程中关闭 STDOUT、STDERR、STDIN 并调用 setenvironment,但它无法正常工作。
sub setEnvironment()
unless ( open(OUT, ">&STDOUT") )
print "Cannot redirect STDOUT";
return 2;
unless ( open(ERR, ">&STDERR") )
print "Cannot redirect STDERR";
return 2;
unless ( open(STDOUT, "|tee -ai $g_LOGPATH/$g_LOGFILE") )
print "Cannot open log file $g_LOGPATH/$g_LOGFILE");
return 2;
unless ( open(STDERR, ">&STDOUT") )
print "Cannot redirect STDERR");
return 2 ;
STDOUT->autoflush(1);
####################### Main Program ######################################
$g_LOGFILE="parent.log";
while ($file = readdir(DIR))
my $pid = fork;
if ( $pid )
setEnvironment();
#parent process code goes here
printf "%s\n", "parent";
next;
$g_LOGFILE="child.log";
setEnvironment();
#child code goes here
printf "%s\n", "child";
exit;
wait for @pids
【问题讨论】:
您正在打印来自父母和孩子的相同消息。难道你的意思是关闭在父级下运行的if ($pid) ...
块中的打印?
刚刚在父子节点中有一些示例 printf 语句。现在编辑了帖子。问题子消息打印在子日志以及父母日志中。我不希望它打印在父母日志中
谁能帮我解决这个问题
为什么不让父进程和子进程打印到单独的文件?
【参考方案1】:
好的,我测试了这段代码。这是我的sample code。在我的代码中存在类似(不准确)的问题:所有消息都双重写入子日志文件。
所以我对你的问题的回答:
问题是子进程 STDOUT 被重定向到父日志文件以及子日志文件。
这是因为当您使用管道 (open(STDOUT, "|tee ...
) 打开文件作为底层结果时,您的进程 fork()
创建子进程,然后将 exec
编程到您运行的程序中 (tee)。 Forking(for tee) 获取主进程的 STDOUT,因此tee
将写入父进程的日志文件。所以我认为你必须撤销对主进程使用 STDOUT 句柄。或者,第二种方法 - 删除 tee
的使用 - 这是最简单的方法。
我也不明白在下面的 setEnvironment 函数中创建 OUT 和 ERR 文件句柄的目的。
似乎这是某人对上述问题的解决方法。您可以grep -rE '
\bERR\b' .
在代码中搜索是否使用。可能有人想保存真正的 STDOUT 和 STDERR 以供进一步使用。
【讨论】:
【参考方案2】:看来原代码的意图如下:
-
当脚本从终端启动时,向终端提供聚合父子输出
此外,在
parent.log
中提供父输出的副本,在child.log
中提供子输出的副本
请注意,就 2. 而言,@Unk 的答案 是正确的,并且与使用 tee
的任何代码相比,移动部件更少,但 未能实现 1。
如果实现 以上 1. 和 2. 都很重要,那么请使用您的原始代码并只需在以下位置添加以下 setEnvironment
方法的顶部:
sub setEnvironment()
if ( fileno OUT )
unless ( open(STDOUT, ">&OUT") )
print "Cannot restore STDOUT";
return 2;
unless ( open(STDERR, ">&ERR") )
print "Cannot restore STDERR";
return 2;
else
unless ( open(OUT, ">&STDOUT") )
print "Cannot redirect STDOUT";
return 2;
unless ( open(ERR, ">&STDERR") )
print "Cannot redirect STDERR";
return 2;
unless ( open(STDOUT, "|tee -ai $g_LOGPATH/$g_LOGFILE") )
...
顺便说一句,如果您的实际代码还没有这样做,请不要忘记将$pid
也添加到@pids
:
...
my $pid = fork;
if ( $pid )
push @pids, $pid;
...
为什么以及如何工作?我们只是想在将其重新连接到tee
之前立即临时恢复原始STDOUT
,以便tee
将其作为其标准输出继承并实际直接写入原始的STDOUT
(例如您的终端)而不是通过父级的tee
(这是子级的STDOUT
通常在此更改之前指向的位置)写入(在分叉子级的情况下),因为继承自paremnt 进程,这就是将那些child
行注入parent.log
的原因。)
因此,在回答您的一个问题时,编写设置OUT
和ERR
的代码的人必须牢记上述内容。 (我不禁想知道您原始代码中缩进的差异是否表明有人在过去 删除了,过去的代码类似于您现在必须添加回来的代码。)
这就是你现在在一天结束时得到的:
$ rm -f parent.log child.log
$ perl test.pl
child
parent
child
parent
parent
child
parent
child
parent
$ cat parent.log
parent
parent
parent
parent
parent
$ cat child.log
child
child
child
child
child
【讨论】:
+1 用于确定原始代码的可能 intent (并纠正我的错字!)。出于某种原因,我曾假设意图是明确将 OUT 和 ERR 用于终端。【参考方案3】:您始终可以通过closing it first and then reopening 将 STDOUT 重定向到日志文件:
close STDOUT;
open STDOUT, ">", $logfile;
这样做的一个小缺点是,一旦 STDOUT 被重定向,在脚本执行期间您将不会在终端上看到任何输出。
如果您希望父进程和子进程具有不同的日志文件,只需在 fork()
之后执行此重定向到不同的日志文件,如下所示:
print "Starting, about to fork...\n";
if (fork())
print "In master process\n";
close STDOUT;
open STDOUT, ">", "master.log";
print "Master to log\n";
else
print "In slave process\n";
close STDOUT;
open STDOUT, ">", "slave.log";
print "Slave to log\n";
我已经测试过这在 Linux 和 Windows 上可以正常工作。
【讨论】:
open(STDOUT,...
已经 dup2
超过 fdes 1,因此在您给出的示例中,close STDOUT
是一个 noop。在open
之前关闭的唯一时间会有所不同(这是不受欢迎的!)是在打开管道open(STDOUT, "|...
时(对pipe
的调用将重用第一个可用的fdes,在这种情况下为1,它将作为一个继承管道的末端以及被孩子视为标准输出,即很可能会发生非常奇怪的事情。)
正确,但明确的close STDOUT
让读者清楚了解真正的意图,并且不会影响其他任何内容。
恕我直言,评论可以使其更加清晰,甚至更清晰。你认为你引入了清晰性,但实际上你引入了一个潜在的错误——同样,当有人将 ">", $logfile
更改为 "|...
时(应该是透明的,对吗?它肯定是在常规的 shell 脚本中),有人在很多地方不愉快的惊喜。永远不要关闭STDxxx
句柄,当open
本身可以期望它们在那里时,它们不是你的可以关闭的。示例:print "OK\n"; close STDOUT; open STDOUT, "|tee log.txt"; print "Still OK???";
-- 你期望它做什么,它真正做什么?【参考方案4】:
#!/usr/bin/perl
use strict;
use warnings;
use utf8;
use Capture::Tiny qw/capture_stdout/;
my $child_log = 'clild.log';
my $parent_log = 'parent.log';
my $stdout = capture_stdout
if(fork())
my $stdout = capture_stdout
print "clild\n";
;
open my $fh, '>', $child_log;
print $fh $stdout;
close $fh;
exit;
print "parent\n";
;
open my $fh, '>', $parent_log;
print $fh $stdout;
close $fh;
【讨论】:
【参考方案5】:所有其他答案都是正确的(尤其是 PSIalt)——我只是希望我能用与问题中的代码非常接近的更正代码来回答。需要注意的关键事项:
"|tee -ai..."
tee 命令将其标准打印到其标准输出,同时也打印到给定文件。正如 PSIalt 所说,删除它是确保每个进程的输出只发送到正确文件的最简单方法。
setEnvironment() 循环内的父级
原始代码不断将 STDOUT 重定向回tee
ed 文件。因此重新捕获 STDOUT。鉴于我在下面的代码,如果您将setEnvironment
移至#parent process code goes here
上方,您会看到除了一个“Real STDOUT”和“Real STDERR”之外的所有内容都实际出现在 parent.log 中。
选项
理想的做法是消除对重定向 STDOUT / STDERR 进行日志记录的任何依赖。我将有一个专用的log($level, $msg)
函数并开始将所有代码移动到使用它。最初,如果它只是现有行为的一个外观是可以的 - 当您达到适当的代码覆盖阈值时,您可以简单地将其切换出来。
如果它是一个基本脚本并且不会产生愚蠢的大日志,为什么不将所有内容打印到 STDOUT 并带有一些您可以使用 grep 的前缀(例如 'PARENT:' / 'CHILD:')?
这有点超出了问题的范围,但请考虑使用更结构化的日志记录方法。我会考虑使用 CPAN 日志记录模块,例如Log::Log4perl。这样,父母和孩子可以简单地请求正确的日志类别,而不是搞乱文件句柄。其他优势:
标准化输出 允许即时重新配置 - 在运行但行为不端的系统上将日志记录级别从 ERROR 更改为 DEBUG 轻松重定向输出 - 无需更改代码即可重新排列日志文件、旋转文件、重定向到套接字/数据库等...use strict;
use warnings;
our $g_LOGPATH = '.';
our $g_LOGFILE = "parent.log";
our @pids;
setEnvironment();
for ( 1 .. 5 )
my $pid = fork;
if ($pid)
#parent process code goes here
printf "%s\n", "parent";
print OUT "Real STDOUT\n";
print ERR "Real STDERR\n";
push @pids, $pid;
next;
$g_LOGFILE = "child.log";
setEnvironment();
#child code goes here
printf "%s\n", "child";
exit;
wait for @pids;
sub setEnvironment
unless ( open( OUT, ">&STDOUT" ) )
print "Cannot redirect STDOUT";
return 2;
unless ( open( ERR, ">&STDERR" ) )
print "Cannot redirect STDERR";
return 2;
unless ( open( STDOUT, '>>', "$g_LOGPATH/$g_LOGFILE" ) )
print "Cannot open log file $g_LOGPATH/$g_LOGFILE";
return 2;
unless ( open( STDERR, ">&STDOUT" ) )
print "Cannot redirect STDERR";
return 2;
STDOUT->autoflush(1);
child.log:
child
child
child
child
child
父日志:
parent
parent
parent
parent
parent
STDOUT 取自终端:
Real STDOUT (x5 lines)
STDERR 取自终端:
Real STDERR (x5 lines)
【讨论】:
以上是关于在子进程中重定向 STDOUT的主要内容,如果未能解决你的问题,请参考以下文章