Process substitution and redirection using tee

Order of redirection is important because Bash applies them in the order it finds them on the command it interprets.

This is on purpose so that you can have idioms like > file 2>&1 working as expected i.e. having stderr the same as stdout. This idiom works as in "assign file to stdout and then make stderr equal to stdout", which yields the expected outcome because by the time stderr gets stdout's same value, stdout's value is file. The other way around (ie 2>&1 1> file) won't yield the same outcome because stdout's value is changed after it has been copied to stderr's value. File-descriptors can be considered analogous to regular variables, which have their own values and can be made to get a copy of another variable's value, as in var1="${var2}", and much like such var1 won't follow var2's subsequent value changes, file-descriptor's value won't too.

It is also handy so that you can e.g. swap file-descriptors on the same line, like in 3>&1 1>&2 2>&3-. This swaps fds 1 and 2 using fd 3 as temporary “helper” fd.

As such you can consider redirections as instructions executed sequentially, just as if they were on two separate lines of your command or script.

On your specific case there are also Process Substitutions involved, and those too get executed in the specified sequence inheriting the redirections expressed up to that point

That is, to cap it all:

  1. you first redirect stdout to the process running tee f.out; at this point cmd’s stdout is connected to tee f.out’s stdin, as desired
  2. then you redirect stderr to the process running tee f.err; but this inherits its stdout as per the redirection expressed before, i.e. connected to tee f.out’s stdin

Therefore tee f.err, by innocently outputting to its stdout as well as to f.err file, pipes your cmd’s error messages to tee f.out’s stdin which will therefore receive all messages, outputting them to f.out file as well as to your terminal window.