简体   繁体   English

在子进程中重定向STDOUT

[英]Redirecting STDOUT in child process

Have a Parent process which spawns multipe child process via fork. 有一个Parent进程通过fork生成multipe子进程。 I want the log files by the parent and child process to be separate. 我希望父进程和子进程的日志文件是分开的。 The Problem is child process STDOUT gets redirected into the parent log file as well as the child log file. 问题是子进程STDOUT被重定向到父日志文件以及子日志文件。 Not sure what i need to change to avoid child process log message to get into the parent log file. 不确定我需要更改什么以避免子进程日志消息进入父日志文件。 Also i dont understand in the below setEnvironment function the purpose of creating OUT and ERR file handle. 另外我不明白在下面的setEnvironment函数中创建OUT和ERR文件句柄的目的。 This is a existing code so i kept as it is. 这是一个现有的代码,所以我保持原样。 In the parent process and child process i set the variable $g_LOGFILE to contain different file name so that separate log files are created. 在父进程和子进程中,我将变量$ g_LOGFILE设置为包含不同的文件名,以便创建单独的日志文件。 Also i call setEnvironment function in both parent and child process. 我也在父进程和子进程中调用setEnvironment函数。 I tried by closing STDOUT,STDERR,STDIN in the child process and calling the setenvironment but it was not working properly. 我尝试在子进程中关闭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

Ok i tested this code alitle. 好吧,我测试了这个代码alitle。 Here is my sample code . 这是我的示例代码 In my code there is similar(not exact) problem: all messages are double-written to childs log file. 在我的代码中存在类似(不完全)的问题:所有消息都被双重写入子日志文件。

So my answers to your questions: 所以我回答你的问题:

The Problem is child process STDOUT gets redirected into the parent log file as well as the child log file. 问题是子进程STDOUT被重定向到父日志文件以及子日志文件。

This because when you open file with pipe ( open(STDOUT, "|tee ... ) as a underlying result your process fork() to create child process and then exec into program what you run (tee). Forking(for tee) takes STDOUT of master process so tee will write into parent's logfile. So i think you must revoke using STDOUT handle for master process. Or, second way - remove use of tee - its simplest way. 这是因为当你用管道打开文件时( open(STDOUT, "|tee ... )作为底层结果你的进程fork()创建子进程,然后exec你运行的程序(tee)。分叉(对于tee)获取主进程的STDOUT,因此tee将写入父进程的日志文件。所以我认为你必须使用STDOUT句柄撤销主进程。或者,第二种方式 - 删除使用tee - 这是最简单的方法。

Also i dont understand in the below setEnvironment function the purpose of creating OUT and ERR file handle. 另外我不明白在下面的setEnvironment函数中创建OUT和ERR文件句柄的目的。

Seems this is someone's woraround about problem above. 似乎这是某人对上述问题的了解。 You can grep -rE ' \\bERR\\b' . 你可以grep -rE ' \\bERR\\b' . to search in code if it used or not. 如果使用或未使用,则搜索代码。 Probably someone wanted to save real STDOUT and STDERR to further use. 可能有人想保存真正的STDOUT和STDERR以供进一步使用。

It would appear that the intent of the original code is as follows: 似乎原始代码的意图如下:

  1. when the script is started from, say, a terminal, then provide aggregate parent and child output to the terminal 当脚本从例如终端开始时,然后终端提供聚合的父输出和子输出
  2. additionally, provide a copy of the parent output in parent.log , and a copy of child output in child.log 另外, parent.log提供父输出的副本,在child.log中提供子输出的child.log

Note that @Unk's answer is correct as far as 2. goes, and has less moving parts than any code using tee , but fails to achieve 1. 请注意, @ Unk的答案是正确的2,并且移动部件比使用tee任何代码少,但是无法实现1。

If it is important to achieve both 1. and 2. above , then take your original code and simply add the following at the top of your setEnvironment method: 如果同时实现上述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") )
    ...

Incidentally, do not forget to also add $pid to @pids if your actual code does not do that already: 顺便说一下,不要忘了还加$pid@pids如果你的实际代码没有做到这一点已经:

  ...
  my $pid = fork;
  if ( $pid ) {
      push @pids, $pid;
      ...

Why and how does this work? 为什么以及如何运作? We simply want to temporarily restore the original STDOUT immediately before rewiring it into tee , so that tee inherits it as its standard output and actually writes directly to the original STDOUT (eg your terminal) instead of writing (in the case of the forked children) through the parent's tee (which is where the child's STDOUT normally pointed to before this change, by virtue of inheriting from the paremnt process, and which is what injected those child lines into parent.log .) 我们只是想暂时恢复原来的STDOUT立即重新布线,然后再将其tee ,让tee继承作为标准输出和实际直接写入到原来的STDOUT (如终端),而不是写(在分叉儿的情况下)通过父母的tee (这是孩子的STDOUT通常在此更改之前指向的地方,因为继承了paremnt进程,这是将这些child行注入parent.log 。)

So in answer to one of your questions, whoever wrote the code to set OUT and ERR must have had exactly the above in mind. 因此,在回答您的一个问题时,无论是谁编写设置OUTERR的代码,都必须考虑到上述问题。 (I cannot help but wonder whether or not the difference in indentation in your original code is indicative of someone having removed , in the past, code similar to the one you have to add back now.) (我不禁想知道原始代码中缩进的差异是否表示有人在过去删除了与您现在必须添加的代码类似的代码。)

Here's what you now get at the end of the day: 这是你现在得到的结果:

$ 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

You can always redirect STDOUT to log file by closing it first and then reopening : 您始终可以先将 STDOUT重定向到日志文件,然后重新打开

close STDOUT;
open STDOUT, ">", $logfile;

Small downside to this is that once STDOUT is redirected, you will not see any output on terminal during script execution. 这样做的一个小缺点是,一旦STDOUT被重定向,在脚本执行期间您将看不到终端上的任何输出。

If you want parent and child process have different log files, just perform this redirection in both to different log files after fork() , something like this: 如果您希望父进程和子进程具有不同的日志文件,只需在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";
}

I have tested that this works as expected on Linux and Windows. 我已经测试过这在Linux和Windows上按预期工作。

#!/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;

All the other answers are correct (PSIalt's in particular) - I'm merely hoping that I can answer with corrected code that is identifiably close to that in the question. 所有其他答案都是正确的(特别是PSIalt) - 我只是希望我能用可以明确接近问题的纠正代码来回答。 The key things to notice: 需要注意的关键事项:

"|tee -ai..." “| tee -ai ......”

The tee commands prints its standard in to its standard out while also printing to the given file. tee命令将其标准打印到其标准输出,同时还打印到给定文件。 As PSIalt says, removing it is the easiest way to ensure each process' output goes only to the correct file. 正如PSIalt所说,删除它是确保每个进程输出仅发送到正确文件的最简单方法。

setEnvironment() inside loop for parent 在父循环内部的setEnvironment()

The original code is constantly redirecting STDOUT back to the tee ed file. 原始代码不断将STDOUT重定向回tee ed文件。 Therefore recapturing STDOUT. 因此重新夺回STDOUT。 Given my code below, if you moved setEnvironment to above #parent process code goes here you would see all but one 'Real STDOUT' and 'Real STDERR' actually appearing in parent.log. 根据下面的代码,如果你将setEnvironment移到#parent process code goes here上面#parent process code goes here你会看到除了一个'Real STDOUT'和'Real STDERR'之外的所有#parent process code goes here实际出现在parent.log中。

Options 选项

The ideal is to remove any reliance on redirecting STDOUT / STDERR for logging. 理想的做法是消除对重定向STDOUT / STDERR进行日志记录的依赖。 I would have a dedicated log($level, $msg) function and start to move all code to using it. 我会有一个专用的log($level, $msg)函数,并开始将所有代码移动到使用它。 Initially it's OK if it is simply a façade for the existing behaviour - you can simply switch it out when you reach an appropriate threshold of code covered. 如果它只是现有行为的外观,那么最初是可以的 - 只要达到适当的代码阈值,就可以将其切换出来。

If it's a basic script and doesn't produce stupidly large logs, why not just print everything to STDOUT with some prefix you can grep for (eg 'PARENT:' / 'CHILD:')? 如果它是一个基本脚本并且不会产生愚蠢的大型日志,那么为什么不只是将所有内容打印到STDOUT,并使用一些前缀(例如'PARENT:'/'CHILD:')?

It's a bit outside the scope of the question, but consider using a more structured approach to logging. 这有点超出了问题的范围,但考虑使用更结构化的方法来记录。 I would consider using a CPAN logging module, eg Log::Log4perl . 我会考虑使用CPAN日志记录模块,例如Log :: Log4perl This way, the parent and children can simply request the correct log category, rather than mess around with file handles. 这样,父级和子级可以简单地请求正确的日志类别,而不是乱用文件句柄。 Additional advantages: 其他优点:

  • Standardise output 标准化输出
  • Allow reconfiguration on the fly - change logging level from ERROR to DEBUG on a running-but-misbehaving system 允许动态重新配置 - 在运行但行为不当的系统上将日志记录级别从ERROR更改为DEBUG
  • Easily redirect output - no change is needed to your code to rearrange log files, rotate files, redirect to a socket / database instead, etc... 轻松重定向输出 - 您的代码无需更改即可重新排列日志文件,旋转文件,重定向到套接字/数据库等等...
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.log:

child
child
child
child
child

parent.log: parent.log:

parent
parent
parent
parent
parent

STDOUT taken from terminal: STDOUT取自终端:

Real STDOUT (x5 lines)

STDERR taken from terminal: STDERR取自终端:

Real STDERR (x5 lines)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM