Search code examples
bashhttp-redirectshstdoutstderr

shell: send grep output to stderr and leave stdout intact


i have a program that outputs to stdout (actually it outputs to stderr, but i can easily redirect that to stdout with 2>&1 or the like.

i would like to run grep on the output of the program, and redirect all matches to stderr while leaving the unmatched lines on stdout (alternatively, i'd be happy with getting all lines - not just the unmatched ones - on stdout)

e.g.

$ myprogram() {
cat <<EOF
one line
a line with an error
another line
EOF
}
$ myprogram | greptostderr error >/dev/null
a line with an error
$ myprogram | greptostderr error 2>/dev/null
one line
another line
$

a trivial solution would be:

myprogram | tee logfile
grep error logfile 1>&2
rm logfile

however, i would rather get the matching lines on stderr when they occur, not when the program exits...

eventually, I found this, which gave me a hint to for a a POSIX solution like so:

greptostderr() {
  while read LINE; do
    echo $LINE
    echo $LINE | grep -- "$@" 1>&2
  done
}

for whatever reasons, this does not output anything (probably a buffering problem).

a somewhat ugly solution that seems to work goes like this:

greptostderr() {
  while read LINE; do
    echo $LINE
    echo $LINE | grep -- "$@" | tee /dev/stderr >/dev/null
  done
}

are there any better ways to implement this?

ideally i'm looking for a POSIX shell solution, but bash is fine as well...


Solution

  • I would use awk instead of grep, which gives you more flexibility in handling both matched and unmatched lines.

    myprogram | awk -v p=error '{ print > ($0 ~ p ? "/dev/stderr" : "/dev/stdout")}'
    

    Every line will be printed; the result of $0 ~ p determines whether the line is printed to standard error or standard output. (You may need to adjust the output file names based on your file system.)