Piping STDERR to STDOUT for filtering, done right

People ask how to pipe STDERR under Bash (the most commonly used shell for Linux users) and get frustrating or bad answers. This is because doing it right is very ugly.

When you want to pipe only STDERR, you probably want to filter STDERR somehow, perhaps using grep or awk or sed, but you don't want to damage the output on STDOUT.

Suppose, for example, that you have a program which produces nice output, but also generates errors on STDERR. Of those errors, there are a few you're interested in, but most simply clutter the output. So you want to filter the STDERR output, but preserve the STDOUT output.

We all know we can redirect STDERR to STDOUT with the syntax

# some_program 2>&1
but this would simply merge STDOUT and STDERR, so downstream you wouldn't know which what was an error and what was plain old output. In bash, you cannot pipe STDERR directly like you can in csh. Instead, you need to flip STDERR and STDOUT, do your filtering, then flip them back.

Before I show you the command, keep in mind two things:

OK. So how do we do this? Well, to flip #2 and #1, we use the trick This creates an entirely new output memory location called #3, and copies the file descriptor of #1 into it. It then puts the file descriptor of #2 in #1. Lastly, it puts the file descriptor of #3 in #2. The order of operations is important. We need to save #1 before we redirect it, and we need make #1 a copy of #2 before we make #2 a copy of #3. Any difference in the order would send the same thing to two places.

The exact same sequence flips them back.

A real world example:

Let's use this to filter tar. The archiving program tar produces a lot of error messages on STDERR, some of which are important and some of which can be ignored. We'd like to see only the ones that show that there's really something wrong.

# tar -cf /tmp/junk.tar /tmp/real /tmp/fake
tar: Removing leading `/' from member names
tar: /tmp/fake: Cannot stat: No such file or directory
tar: Error exit delayed from previous errors

The directory /tmp/real exists, but the directory /tmp/fake doesn't. The other messages are things we always see. We'd rather only see the message about /tmp/fake. We do this using egrep.

# tar -cf /tmp/junk.tar /tmp/real /tmp/fake 3>&1 1>&2 2>&3 | egrep -v "tar: Removing leading|tar: Error exit delayed from previous errors"
tar: /tmp/fake: Cannot stat: No such file or directory

Better. But STDERR is now on STDOUT and vice versa. We should flip them back. And here's where my second point from above comes in. You can't just add the flipping syntax at the end of the egrep command. The egrep command has it's own STDERR location, distinct from the one tar is using. You need to bunch them together before flipping them back. You do this by using parenthesis.

# (tar -cf /tmp/junk.tar /tmp/real /tmp/fake 3>&1 1>&2 2>&3 | egrep -v "tar: Removing leading|tar: Error exit delayed from previous errors") 3>&1 1>&2 2>&3
tar: /tmp/fake: Cannot stat: No such file or directory


Yes, it's a mess. But now that you have the pattern, you can use it when you really need it. For example, when you only want to see certain types of STDERR output in cron jobs. You can put the above syntax in lines of your crontab, or you can wrap your commands in scripts, so as not to junk up your crontab file.

In a nutshell, it's:

( COMMAND 3>&1 1>&2 2>&3 | grep -v ANNOYING_ERRORS ) 3>&1 1>&2 2>&3 | grep TARGET_STRINGS


Paul Pomerleau is the author of Networking for English Majors, which teaches people with liberal arts and social science degrees how to get jobs in computer networking. You don't need a computer science degree to get a well-paying job in computing.