1

我有一个分叉子进程的 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 重定向到文件的守护程序)以使用时间戳。

谢谢

4

2 回答 2

2

假设你写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: $!";
  }
}

要点在以下文档open中进行了描述:

如果您在命令上打开一个管道-(即,指定一个|--|两个参数形式的 open),则执行隐式fork操作,因此 open 返回两次:在父进程中,它返回子进程的 pid ,并在子进程中返回 (a defined) 0。使用defined($pid)//确定打开是否成功。

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

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

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

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

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

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

完整示例的代码如下所示。你提到了一个守护进程,所以我启用了Perl 的数据流分析,称为 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。
于 2013-11-06T00:06:14.667 回答
1

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);
于 2013-11-05T19:46:26.770 回答