Linux – Obtain both merged and separated stdout and stderr

bashlinuxredirection

I am looking for a way to run a program Prog and end up with 3 files:

  1. The stdout of Prog
  2. The stderr of Prog
  3. Both stdout and stderr of Prog combined just as they would be on screen if no redirecting took place.

Is there a combination of redirecting, pipes etc. which could achieve this?

Note: I normally use bash.

Best Answer

As far as I know, there is no elegant way of doing this (for an inelegant way that works but ain't pretty, scroll down to the end of my answer). I doubt you can do any better than:

$ command >stdout.txt 2>stderr.txt && cat stdout.txt stderr.txt > both.txt

There are various cool tricks you can use but none of them seems to succeed in producing the 3 files any better than the above. The main problem is that the file both.txt will not show the messages (STDERR and STDOUT) in the correct order. This is because (as explained here):

When you redirect both standard output and standard error to the same file, you may get some unexpected results. This is due to the fact that STDOUT is a buffered stream while STDERR is always unbuffered. This means that every character of STDERR is written as soon as it is available while STDOUT writes stuff in batches. When both STDOUT and STDERR are going to the same file you may see error messages appear sooner than you would have expected them in relation to the actual output of your program or script. It isn’t anything to be alarmed about but is simply a side-effect of buffered vs. unbuffered streams, you just need to keep it in mind.

The best alternative I could find was using bash subshells, is kind of complex and still does not display the output in the correct order. I made a simple Perl script, test.pl that prints "OUT" to STDOUT and "ERR" to STDERR, repeating the process 3 times:

#/usr/bin/perl 
for($i=0; $i<=2; $i++){
    print STDOUT "OUT\n"; 
    print STDERR "ERR\n"
}

Its normal, un-redirected output is :

$ ./test.pl
OUT
ERR
OUT
ERR
OUT
ERR

To redirect output(s) I ran:

(./test.pl 2> >(tee error.txt) > >(tee out.txt)) > both.txt 

This uses tee, a program that will print its input to screen and to a file name. So, I am redirecting STDERR and passing it as input to tee, telling it to write it to the file error.txt. Similarly with STDOUT and the file out.txt. I am placing the whole thing in a subshell ((...)) so I can then capture all of its output and redirect to both.txt.

Now, this works inasmuch as it creates 3 files, one with STDERR, one with STDOUT and one with both. However, as explained above, this results in the messages appearing in the incorrect order in both.txt:

$ cat both.txt 
ERR
ERR
ERR
OUT
OUT
OUT

The only way around this I could find was to append the time it was printed to each line of output and then sorting, but it is getting seriously convoluted and, in your place, I would ask myself if it is really worth it:

 $(./test.pl \
   2> >(while read n; do echo `date +%N`" $n"; echo "$n" >>error.txt; done) \
    > >(while read n; do echo `date +%N`" $n"; echo "$n" >> out.txt; done )) \
 | gawk '{print $2}'> both.txt 
Related Question