在子进程中重定向STDOUT

时间:2012-11-01 05:52:00

标签: perl

让父进程通过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

5 个答案:

答案 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)

似乎原始代码的意图如下:

  1. 当脚本从终端开始,然后向终端提供汇总的父输出和子输出
  2. 另外,在parent.log中提供父输出的副本,并在child.log
  3. 中提供子输出的副本

    请注意, @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 -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.log:

parent
parent
parent
parent
parent

STDOUT取自终端:

Real STDOUT (x5 lines)

STDERR取自终端:

Real STDERR (x5 lines)