【问题标题】:Modifying output of child process to add a timestamp修改子进程的输出以添加时间戳
【发布时间】:2013-11-16 18:36:21
【问题描述】:

我有一个分叉子进程的 perl 脚本。

sub my_exec{
    my($args,$stdout, $stderr) = @_;
    my $processes = fork();
    die("Cant fork") unless defined($processes);
    if(processes == 0){
        if(defined $stdout){
            close(STDOUT);
            open STDOUT, $stdout;
        }
        if(defined $stderr){
            close(STDERR);
            open STDERR, $stderr;
        }
        exec @$args;
    }else{
                ...
    }
}

我的主要问题是我想为 stderr 的每一行输出添加一个时间戳。我想知道是否可以在这里完成。如您所见,stderr 并不总是改变。我假设我可以通过某种管道来做到这一点?我还想重定向父脚本(同时将 stdout 和 stderr 重定向到文件的守护程序)以使用时间戳。

谢谢

【问题讨论】:

  • open STDOUT,$stdout 打开一个输入文件句柄——不是你想要的。
  • 好吧,$stdout 里面会有类似 ">out.log" 的东西
  • IPC::Run 可能是你想要的
  • 谢谢。我肯定在看 IPC::Run。但这可以用我目前运行东西的方式(fork 和 exec)来完成吗?
  • @Smartelf 是的,你可以用低级的方式来做。见我的answer below

标签: linux perl unix fork pipe


【解决方案1】:

假设你写my_exec如下。

sub my_exec {
  my($args,$stdout,$stderr) = @_;  # caller untaints

  open my $oldout, ">&STDOUT" or die "$0: save STDOUT: $!";

  my $pid = open my $pipe, "-|" // die "$0: fork: $!";
  if ($pid) {
    if (defined $stderr) {
      open STDERR, ">", $stderr or die "$0: open: $!";
    }
    while (<$pipe>) {
      print STDERR scalar(localtime), ": ", $_;
    }
    close $pipe or die $! ? "$0: error closing $args->[0] pipe: $!"
                          : "$0: exit status " . ($? >> 8) . " from $args->[0]";
  }
  else {
    open STDERR, ">&STDOUT"    or die "$0: pipe STDERR: $!";
    if (defined $stdout) {
      open STDOUT, ">", $stdout or die "$0: open: $!";
    }
    else {
      open STDOUT, ">&", $oldout or die "$0: restore STDOUT: $!";
    }
    exec @$args or die "$0: exec @$args: $!";
  }
}

重点在documentation on open中描述:

如果您在命令 - 上打开管道(即,使用 open 的一个或两个参数形式指定 |--|),则完成隐式 fork,因此 open返回两次:在父进程中返回子进程的 pid,在子进程中返回(已定义)0。使用defined($pid)//判断打开是否成功。

隐式fork的重点是在父进程和子进程之间建立管道。

父进程的文件句柄表现正常,但该文件句柄的 I/O 是从子进程的STDOUT 管道传输的。在子进程中,文件句柄没有打开——I/O 来自新的STDOUT

这几乎是完美的,除了你想修改标准错误,而不是标准输出。

这意味着我们需要保存父母的STDOUT,以便孩子可以恢复它。这就是 $oldout 正在发生的事情。

将子级的(重定向的)STDOUT 复制到其STDERR 上可以安排底层守护程序的标准错误通过管道运行,父级读取、修改和输出管道。

一个有点棘手的地方是处理重定向的地方。如果调用者想要重定向STDOUT,这需要在孩子身上发生。但是要重定向STDERR,父级需要这样做,因为这给了父级修改流的机会。

完整示例的代码如下所示。你提到了一个守护进程,所以我启用了Perl’s dataflow analysis known as taint mode

#! /usr/bin/perl -T

use strict;
use warnings;

use v5.10.0;  # for defined-or //

$ENV{PATH} = "/bin:/usr/bin";

sub my_exec {
  # paste code above
}

#my_exec ["./mydaemon"];
#my_exec ["./mydaemon"], "my-stdout";
my_exec ["./mydaemon"], "my-stdout", "my-stderr";

用一个简单的mydaemon

#! /usr/bin/env perl

print "Hello world!\n";
warn  "This is a warning.\n";
die   "All done.\n";

输出到单独的文件。

1. my-stdout:

世界你好!

2。 my-stderr:

2013 年 11 月 5 日星期二 17:58:20:这是一个警告。
2013 年 11 月 5 日星期二 17:58:20:全部完成。
./wrapper:在 ./wrapper 第 23 行从 ./mydaemon 退出状态 255。

【讨论】:

  • 谢谢。那是一个非常详细的答案。 2 快速提问。 1)这个功能会等待孩子完成,对吗? (看起来像,只是确保) 2)等待不会影响 perls 信号处理(也似乎很好)。我不只使用系统命令的主要原因是我需要能够在孩子运行时拥有一个响应式信号处理程序。我只是想确保我没有遗漏任何东西。再次感谢。
  • @Smartelf 1) while 循环读取$pipe 上的所有输出,并假设在readline 失败时孩子已经退出。如果您担心子进程会在退出之前关闭其标准错误,您可以在调用close 之前添加一个额外的waitpid。 2) 假设您使用的是 perl v5.8.0 或更高版本,由于延迟信号,您应该处于良好状态。见“Interrupting IO” in the perlipc documentation
【解决方案2】:

fork 级别太低了。 IPC::Open3 是您应该使用的最低要求。

use IPC::Open3 qw( open3 );

open(local *CHILD_STDIN, '<', '/dev/null') or die $!;
open(local *CHILD_STDOUT, '>', $stdout) or die $!;

my $pid = open3(
   '<&CHILD_STDIN',
   '>&CHILD_STDOUT',
   \local *CHILD_STDERR,
   $prog, @$args
);

open(my $stderr_fh, '>', $stderr) or die $!;
while (<CHILD_STDERR>) {
   print $stderr_fh $ts . $_;
}

waitpid($pid, 0);

【讨论】:

  • prob 想要 use POSIX 然后 $ts = strftime("%F %T", localtime); 或类似的 CHILD_STDERR 循环
  • 或者在循环之前。人们通常希望整个过程使用相同的时间戳。
猜你喜欢
  • 2016-12-29
  • 2018-01-24
  • 2019-10-02
  • 1970-01-01
  • 2014-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多