让父进程通过fork生成multipe子进程。我希望父进程和子进程的日志文件是分开的。问题是子进程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
答案 0 :(得分:3)
好的,我测试了这段代码alitle。这是我的sample code。在我的代码中存在类似(不完全)的问题:所有消息都被双重写入子日志文件。
所以我回答你的问题:
问题是子进程STDOUT被重定向到父日志文件以及子日志文件。
这是因为当您使用管道(open(STDOUT, "|tee ...
)打开文件作为基础结果时,您的进程fork()
将创建子进程,然后exec
进入您运行的程序(tee)。分叉(对于tee)采用主进程的STDOUT,因此tee
将写入父进程的日志文件。所以我认为你必须使用STDOUT句柄撤销主进程。或者,第二种方式 - 删除tee
的使用 - 这是最简单的方法。
另外我在下面的setEnvironment函数中并不了解创建OUT和ERR文件句柄的目的。
似乎这是某人对上述问题的了解。您可以grep -rE '
\bERR\b' .
在代码中搜索是否使用过。可能有人想保存真正的STDOUT和STDERR以供进一步使用。
答案 1 :(得分:1)
似乎原始代码的意图如下:
parent.log
中提供父输出的副本,并在child.log
请注意, @Unk的回答在2个版本中是正确的,移动部件比使用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;
...
为什么以及如何运作?我们只想在将STDOUT
重新连接到tee
之前立即暂时还原原始tee
,以便STDOUT
将其作为其标准输出继承并且实际上直接写入原始的tee
(例如您的终端),而不是通过父亲的STDOUT
(通常是孩子的child
的位置来写(在分叉的孩子的情况下)在此更改之前指出,由于继承了paremnt进程,并且将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}}
答案 2 :(得分:0)
您始终可以通过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上按预期工作。
答案 3 :(得分:0)
#!/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;
答案 4 :(得分:0)
所有其他答案都是正确的(特别是PSIalt) - 我只是希望我可以使用与问题中明确接近的更正代码来回答。需要注意的关键事项:
tee命令将其标准输出到其标准输出,同时还打印到给定文件。正如PSIalt所说,删除它是确保每个进程输出仅传递给正确文件的最简单方法。
原始代码不断将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。这样,父级和子级可以简单地请求正确的日志类别,而不是乱用文件句柄。其他优点:
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.log:
parent
parent
parent
parent
parent
STDOUT取自终端:
Real STDOUT (x5 lines)
STDERR取自终端:
Real STDERR (x5 lines)