Skip to content. | Skip to navigation

Navigation

You are here: Home / Support / Guides / Scripting / Bash / Redirecting stdout/stderr / Successfully Redirecting stdout/stderr

Personal tools

Redirecting stdout/stderr

Redirecting stdout and stderr to a file and have stdout and stderr available to redirect as normal.

Successfully Redirecting stdout/stderr

Can we get ourselves out of this stdout/stderr competing processes mess? Yes we can but we want to flip things upside down.

One thing we do know works for redirecting stdout and stderr to a file without problems is to get the shell to do it for us:

cmd args > file 2>&1

That form works a treat so we should use it. So, let's rethink calling cmd args then:

wrapped-cmd > file 2>&1

{rubs hands together} Great!

How to we get wrapped-cmd to write to stdout and stderr then? Why, we use tee of course! But don't we end up in the same mess as before? Not if we don't care that we're writing to separate files -- our problem before was that the tee processes were writing to the same file.

Think back to File Descriptor 101 where we were making "dup"s of file descriptors (or rather we were pointing handy spare file descriptors at the same "buckets" where the original descriptors point to). How about if we make "dup"s of stdout and stderr which, if you recall, don't serve much use as other than a placeholder for later.

Well, there's another neat side effect on modern Unix OS': when you create a file descriptor the equivalent entry appears in the file system. tee wants a file to write to so, let's combine the two.

Before we kick off wrapped-cmd let's "dup" stdout and stderr and then use the file system names as the targets for the two tee processes:

exec 3>&1 4>&2
wrapped-cmd > file 2>&1

In wrapped-cmd, we don't care that the two tee processes are are separate files as we are ultimately expecting them to be handled as two different streams. On a moment by moment basis, give or take the time it takes tee to read from stdin and write to its stdout, the stdout and stderr streams will appear on their original devices (the terminal, a file, /dev/null) in the order and with the expediency you'd expect -- ie, stderr will remain write() buffered and stdout will be line buffered for a terminal).

Let's see wrapped command, then:

cmd args > >(tee /dev/fd/3) 2> >(tee /dev/fd/4 >&2)

So, we have the first tee command output to both wherever stdout has been inherited from and to the original stdout.

Aren't they the same? No, you recall that we are running wrapped-cmd as:

wrapped-cmd > file 2>&1

so wrapped-cmd's stdout is going to be file and not the original stdout.

Ditto for stderr and its filesystem dupe /dev/fd/4.

On top of all that, wrapped-cmd can be a subshell (rather than a genuinely separate script) giving us all the advantages of not having to handle whitespaced arguments.

So, pulling it all together we have:

exec 3>&1 4>&2
(
 cmd args > >(tee /dev/fd/3) 2> >(tee /dev/fd/4 >&2)
) >file 2>&1

With all that in some command, foo, you can say:

foo >/dev/null

and you'll only see stderr and have a copy of both stdout and stderr in order in a file (file).

Top!

Document Actions