Pipe only STDERR through a filter

Viewed 36731

Is there any way, in bash, to pipe STDERR through a filter before unifying it with STDOUT? That is, I want

STDOUT ────────────────┐
                       ├─────> terminal/file/whatever
STDERR ── [ filter ] ──┘

rather than

STDOUT ────┐
           ├────[ filter ]───> terminal/file/whatever
STDERR ────┘
7 Answers

TL;DR:

$ cmd 2> >(stderr-filter >&2)

Example:

% cat /non-existant 2> >(tr o X >&2)
cat: /nXn-existant: NX such file Xr directXry
%

This will work in both bash and zsh. Bash is pretty much ubiquitous these days, however, if you really do need a (really gnarly) solution for POSIX sh, then see here.


Explanation

By far, the easiest way to do this is to redirect STDERR via process substitution:

Process substitution allows a process’s input or output to be referred to using a filename. It takes the form of

>(list)

The process list is run asynchronously, and its input or output appears as a filename.

So what you get with process substituion is a filename.

Just like you could do:

$ cmd 2> filename

you can do

$ cmd 2> >(filter >&2)

The >&2 redirect's filter's STDOUT back to the original STDERR.

TL;DR: (bash and zsh)

$ cmd 2> >(stderr-filter >&2)

Example:

% cat /non-existant 2> >(tr o X >&2)
cat: /nXn-existant: NX such file Xr directXry
%

Many answers on the StackExchange network have the form:

cat /non-existant 3>&1 1>&2 2>&3 3>&- | sed 's/e/E/g'

This has a built-in assumption: that file descriptor 3 isn't being used for something else.

Instead, use a named file descriptor, and {ba,z}sh will allocate the next available file descriptor >= 10:

cat /non-existant {tmp}>&1 1>&2 2>&$tmp {tmp}>&- | sed 's/e/E/g'

Note that named file descriptors aren't supported by POSIX sh.

The other issue with the above is that the command cannot be piped to further commands without again swapping STDOUT and STDERR back to their original values.

To allow onward piping in POSIX sh, (and still assuming FD 3 is not it use) it gets complicated:

(cmd 2>&1 >&3 3>&- | stderr-filter >&2 3>&-) 3>&1

So, Given the assumption and gnarly syntax of this, you're likely to be better off using the simpler bash/zsh syntax shown in the TL;DR above, and explained here.


practical demonstration, grepping only stderr:

$ ls -l . noexistABC noexistXYZ
ls: cannot access 'noexistABC': No such file or directory
ls: cannot access 'noexistXYZ': No such file or directory
.:
total 4
-rw-rw-r-- 1 frank frank    0 Aug 19 12:26 bar.txt
-rw-rw-r-- 1 frank frank    0 Aug 19 12:26 foo.txt
drwxrwxr-x 2 frank frank 4096 Aug 19 12:26 someFolder


$ ( ls -l . noexistABC noexistXYZ 2>&1 >&3 3>&- | grep ABC >&2 3>&-) 3>&1
.:
ls: cannot access 'noexistABC': No such file or directory
total 4
-rw-rw-r-- 1 frank frank    0 Aug 19 12:26 bar.txt
-rw-rw-r-- 1 frank frank    0 Aug 19 12:26 foo.txt
drwxrwxr-x 2 frank frank 4096 Aug 19 12:26 someFolder
Related