5

If I have a process a.out I can do ./a.out | grep foo to see stdout of a.out filtered by foo. I can also say ./a.out 2>&1 | grep foo to see both err and out filtered by foo. With the tee command I can send stdout to both the terminal and possibly a file output. But is there a way to filter those separately? as in:

./a.out | tee grep foo file.txt

but such that what goes to file.txt is filtered to match foo but not what I see on the screen...or even better what I see on the screen gets filtered by baz instead of foo? If there isn't a way to do so in bash already I would write my own "tee" but I would imagine there is some way...

Palace Chan
  • 8,845
  • 11
  • 41
  • 93

3 Answers3

3

To display everything to the terminal, and filter output to a file, try:

./a.out| tee /dev/tty | grep foo > file

If you are on a system with a /proc filesystem (eg, linux), you can filter the output to your terminal with:

{ ./a.out | tee /proc/self/fd/3 | grep foo > file; } 3>&1 | grep bar

But even that is probably working too hard. Just do this:

./a.out | awk '/foo/{ print > "file" } 1' | grep bar
William Pursell
  • 204,365
  • 48
  • 270
  • 300
  • How does that last one work? awk matches foo from a.out's stdout and prints those lines to "file"..but how do they reach the grep bar? Im guessing it has to do with the 1 at the end of the awk script? – Palace Chan Aug 31 '12 at 03:58
  • The `1` at the end of the script causes every line to be printed. In actual use, instead of piping things to `grep bar`, you would just write `/bar/` instead of `1`. – William Pursell Aug 31 '12 at 22:04
3

Very easy, just use process substitution for your file handles:

./a.out | tee >(grep foo > out.txt) | grep baz

Note also that tee can take as many arguments as you like, so you can do things like:

./a.out | tee >(grep foo > foo.txt) >(grep bar > bar.txt) [etc]
Grisha Levit
  • 8,194
  • 2
  • 38
  • 53
  • So that says stdout goes to the $outfilter > out.txt filter, and that stderr goes to the $errfilter > err.txt filter...but can I use process substitution to do two different things with stdout? like have it both go to out.txt and to terminal? – Palace Chan Aug 31 '12 at 04:00
  • You're right, I've adjusted the answer to answer the question properly. Just add as many arguments to `tee` as you have things to do with the `stdout` – Grisha Levit Aug 31 '12 at 06:51
  • Oh cool! I'm a little confused why it's tee <(grep food > out.txt) | grep baz...isn't <(bla) saying to tee, take your input from the process in the <() construct? It makes sense if i think of the <(grep foo > out.txt) as an argument file instead, but i thought <() and >() respectively meant "take stdin from the output of the command in <()" and "pass your stdout to the command in the >()"? – Palace Chan Aug 31 '12 at 21:16
  • Palace, you're right, that was a typo. The construct is definitely `tee >(filter1 > file1) >(command2 > file2)`, etc. – Grisha Levit Sep 01 '12 at 05:31
2
{ { ( myCommandThatOutputsOnStdOutandStdErr; ) \
| ( awk ' ... filters stdout ... ' - ; ) >&3; } 2>&1 \
| ( awk ' ... filters stderr ... ' - ; ) >&4; } 3>&1 4>&2

This is pseudocode. The awk blurbs aren't real, but can be replaced by your own filter. The idea is that two separate filters can operate on stderr and stdout independently, yet leave what is not filtered on stdout on stdout and what is not filtered on stderr on stderr.

The final 3>&1 and 4>&2 could just as easily be something like 3>my.stdout and 4>my.stderr.

kbulgrien
  • 4,384
  • 2
  • 26
  • 43