5

I can redirect stdout into a command redirection well enough:

$ ( echo stdout ; >&2 echo stderr  ) > >( rev )
stderr
tuodts

but when I try to redirect stderr into a different one, it seems to go through the stdout channel:

$ ( echo stdout ; >&2 echo stderr  ) > >( rev ) 2> >( cat )
tuodts
rredts

I can't use pipe because I want to keep the processing strictly separate:

$ ( echo stdout ; >&2 echo stderr ) 2> >(rev)
stdout
rredts

and this does weird things:

$ ( echo stdout ; >&2 echo stderr ) 2> >(rev) | cat -n
 1  stdout
 2  rredts

Am I misunderstanding how this should work?

(My actual goal is to have stdout and stderr go through two different tee processes, whilst remaining distinctly stderr and stdout for the existing consuming process.)

GNU bash, version 3.2.57(1)-release-(x86_64-apple-darwin15)

android.weasel
  • 3,343
  • 1
  • 30
  • 41
  • Why are you doing it like that instead of piping ? – 123 Jul 07 '16 at 08:36
  • Because I don't want the pipe to capture both outputs – android.weasel Jul 07 '16 at 08:38
  • What is your goal here then? – 123 Jul 07 '16 at 08:39
  • My overall goal is to tee stdout and stderr to different files, while letting both continue to print to stderr and stdout distinctly. But the behaviour above is perplexing and I'm very interested to understand it. – android.weasel Jul 07 '16 at 08:42
  • `{ echo stdout ; >&2 echo stderr ;} 2> >(tee err >/dev/tty) | tee out` ? – 123 Jul 07 '16 at 08:49
  • Try the code, stderr will not be in stdouts file... – 123 Jul 07 '16 at 08:52
  • 1
    I did - that's why I deleted my remark. I prefer the >&2 of John1024's suggestion below to /dev/tty, though: my command output is being processed by something else, which is why I want to preserve the separation, and I'm not sure /dev/tty would send the output where it should go. – android.weasel Jul 07 '16 at 08:54
  • Whatever you want to think... – 123 Jul 07 '16 at 08:55

1 Answers1

6

When doing redirections, order is important.

You need to reverse the order:

$ ( echo stdout ; >&2 echo stderr  ) 2> >( cat ) > >( rev )
stderr
tuodts

Discussion

Consider the original code:

$ ( echo stdout ; >&2 echo stderr  ) > >( rev ) 2> >( cat )
tuodts
rredts

The stderr does get passed to cat but cat sends its output to stdout and stdout has already been redirected to rev. Hence, both stdout and stderr eventually go to rev.

If we reverse the order (as above), cat sends it output to the terminal because stdout has not yet been redirected.

Alternative

Here is another way to keep stderr from getting captured by rev:

$ ( echo stdout ; >&2 echo stderr  ) > >( rev ) 2> >( cat >&2)
tuodts
stderr
John1024
  • 109,961
  • 14
  • 137
  • 171
  • 1
    Aaaargh! Thanks for a very clear explanation. I like the alternative solution - it's more explicit. – android.weasel Jul 07 '16 at 08:49
  • This *can* mess up the order that they are displayed to screen, there is no way to keep it consistent using this method. – 123 Jul 07 '16 at 08:50
  • Yes, that is true: once there is process substitution, order cannot be guaranteed. For one, in a multitasking environment, there is no guarantee that `rev` or `cat` will receive shares of processor time in any particular order. For another, once the channels are separated, there is the vagary of buffering. – John1024 Jul 07 '16 at 08:55
  • You may also want to use a code block instead of opening a subshell for no reason. – 123 Jul 07 '16 at 09:04
  • 1
    I understand it's likely that stdout and stderr are liable to buffering and other issues. This is only a temporary addition to prove whether a load of cruft that occasionally appears in a Teamcity log as red stderr output is caused by Teamcity, or is somehow coming from my script, while leaving things largely unchanged for the devs. – android.weasel Jul 07 '16 at 09:44
  • This actually highlights a slight problem(?) with process substitution, or at least poor documentation. In `bash`, the process in the substitution appears to inherit its standard output from the command in which it appears, so the previous output redirection applies. In `zsh`, the substitution appears to inherit its standard output from the calling shell (which make a little more sense to me), so the OP's original code works. – chepner Jul 07 '16 at 11:57