Why is there a "/dev/fd/63" in the output of "echo 123 >(cat)"?

The process substitution >(thing) will be replaced by a file name. This file name corresponds to a file that is connected to the standard input of the thing inside the substitution.

The following would be a better example of its use:

$ sort -o >(cat -n >/tmp/out) ~/.profile

This would sort the file ~/.profile and send the output to cat -n which would enumerate the lines and store the result in /tmp/out.

So, to answer you question: You get that output because echo gets the two arguments 123 and /dev/fd/63. /dev/fd/63 is the file connected to the standard input of the cat process in the process substitution.

Modifying your example code slightly:

$ echo 101 > >(cat)

This would produce just 101 on standard output (the output of echo would be redirected to the file that serves as the input to cat, and cat would produce the contents of that file on standard output).


Also note that in the cmd1 | cmd2 pipeline, cmd2 may not at all be running in the same shell as cmd1 (depending on the shell implementation you are using). ksh93 works the way you describe (same shell), while bash creates a subshell for cmd2 (unless its lastpipe shell option is set and job control is not active).


For completeness

cmd1 >(cmd2)

is mostly the same as

cmd1 | cmd2

in the yash shell, and that shell only.

In that shell, >(cmd) is process redirection as opposed to the >(cmd) of ksh/bash/zsh which is process substitution.

It's not strictly equivalent, because in cmd1 >(cmd2), yash doesn't wait for cmd2, so you may find that:

$ yash -c 'echo A >(cat); echo B'
B
A
$ yash -c 'echo A | cat; echo B'
A
B

By contrast, process substitution expands to a file path (typically a named pipe or a /dev/fd/<x> where <x> is a fd to a pipe that has been created beforehand) which, when open for writing will allow to send output to cmd.

While process substitution was introduced by ksh, in that shell, you can't pass them as argument to redirections.

ksh -c 'cmd1 > >(cmd2)'

to emulate yash process redirection won't work. There, you're meant to pass that file name resulting from the substitution as argument to a command like in:

ksh -c 'diff <(echo a) <(echo b)'

It will work in bash and zsh.

However, in bash like for yash's process redirection, the shell doesn't wait for the command (cmd2). So:

$ bash -c 'echo A > >(cat); echo B'
B
A

ksh process substitution can be emulated with yash with:

cmd1 /dev/fd/5 5>(cmd2)   

Like:

diff /dev/fd/3 3<(echo a) /dev/fd/4 4<(echo b)

Because that's what process substitution does, it makes the command inside the substitution appear as a filename. Internally, it connects the commands via a pipe, giving the /dev/fd/NN path to the main command, so it can open the already-open file descriptor to the pipe.

It's not the same as a pipe. Pipes connect stdout to stdin without involving anything that looks like file names. Process substitution is more flexible in that you can have multiple such substitutions in one command line, but it requires the main command to open files by name (e.g. cat rather than echo).

You can emulate a pipe with process substitution by doing something like this:

echo foo > >(cat -n)