Search code examples
bashhttp-redirectstdoutstderrtee

stdout to file1, stderr to file2, both correctly interleaved to stdout and file


Given a third-party program, how would one simultaneously:

  1. write stdout to z.stdout
  2. write stderr to z.stderr
  3. pass exit codes appropriately
  4. write both in correct interleaved order to stdout

Here's the test program (delayed_interleaved_stdout_stderr.pl) I've been using:

#!/usr/bin/env perl

use strict;
use warnings;

# fixme: debug, uncomment to force stdout flushing
# use English '-no_match_vars';
# $OUTPUT_AUTOFLUSH = 1;

# use sleeps to simulate delays and test buffering
use Time::HiRes 'sleep';

foreach my $num ( 0..9 ) {
  if ( 0 == $num % 2 ) {
    print STDOUT $num, ":stdout\n";
  }
  else {
    print STDERR $num, ":stderr\n";
  }
  sleep 0.25;
}

So far I've been able to do 1,2,3 with:

( set -o pipefail; \
  ( set -o pipefail; delayed_interleaved_stdout_stderr.pl \
    | tee z.stdout; exit $? \
  ) 3>&1 1>&2 2>&3 | tee z.stderr; exit $? \
) 3>&1 1>&2 2>&3

Thanks to a related answer by lhunath and a friend, I simplified it to:

delayed_interleaved_stdout_stderr.pl > >(tee z.stdout) 2> >(tee z.stderr >&2)

However, I haven't been able to get correct interleaved order. stderr prints immediately and the (presumably buffered) stdout all prints at the end.

1:stderr
3:stderr
5:stderr
7:stderr
9:stderr
0:stdout
2:stdout
4:stdout
6:stdout
8:stdout

Running delayed_interleaved_stdout_stderr.pl by itself displays in the proper 0-9 order. Forcing stdout to flush works appropriately (see commented fixme section), but I won't be able to modify the real files.

Maybe I'm missing something basic and I'm beginning to wonder if this is possible at all :(


Solution

  • I confirmed I can enforce stdout flushing for the third-party programs. In light of this, I'm going with

    delayed_interleaved_stdout_stderr.pl > >(tee z.stdout) 2> >(tee z.stderr >&2)
    

    Thanks for all help!