views:

95

answers:

2

In perl, after fork()ing I can redirect a child's stdout to a file like so

open STDOUT,">",$filename or die $!

I'm wondering if there is a way of "copying it", keeping the stdout on the parent's stdout but also copying to a specified file. It should happen in a way that would not require any buffering and the user would see the console output in real time. This would sort of be like the unix tee. But ideally the solution would not involve any third party libraries.

+2  A: 

In the child, execute

open STDOUT, "|-", "tee", $output
  or die "$0: failed to start tee: $!";

If you don't want to use tee for some reason, you could use a poor man's version by forking another child via open STDOUT, "|-" and doing the duplication in the grandchild:

#! /usr/bin/perl

use warnings;
use strict;

my $pid = fork;
die "$0: fork: $!" unless defined $pid;

if ($pid) {
  waitpid $pid, 0;
}
else {
  my $pid = open STDOUT, "|-";
  die "$0: fork: $!" unless defined $pid;
  select STDOUT; $| = 1;  # disable STDOUT buffering
  if ($pid) {
    print "Hiya!\n";
    system "echo Howdy";
    close STDOUT or warn "$0: close: $!";
    exit 0;
  }
  else {
    open my $fh, ">", "/tmp/other-output" or die "$0: open: $!";
    my $status;
    while ($status = sysread STDIN, my $data, 8192) {
      syswrite $fh, $data and print $data or die "$0: output failed: $!";
    }
    die "$0: sysread: $!" unless defined $status;
    close $fh or warn "$0: close: $!";
    exit 0;
  }
}

Sample run:

$ ./owntee 
Hiya!
Howdy

$ cat other-output 
Hiya!
Howdy
Greg Bacon
A: 

If the parent can wait around for the child to finish, the child could pipe its output back to the parent, which takes responsibility for printing it to both STDOUT and the destination file:

open my $destfile, '>', $path or die "Can't open destination file: $!\n";

my $pid = open my $child, '-|';
defined $pid or die "Can't fork: $!\n";

if ($pid == 0) {
    # Child process:
    print "I'm the child!\n";
    close $destfile;
    #  do whatever
    exit;
}

# Parent process:

while (<$child>) {
    print STDOUT    $_;
    print $destfile $_;
}

close $child;
close $destfile;

Actually, even if the parent can't wait around for the child to finish, this strategy would still work if the parent first forked a child to perform the logic outlined above.

Sean