5
$ echo 123 | cat 
123

is doing what I expected, both commands run inside the same shell.

But when I connect them with the >( ... ) expression, which connects the output of one command in the shell to a second in a subshell, I get this:

$ echo 123 >(cat)
123 /dev/fd/63

this is also true with other values:

$ echo 101 >(cat)
101 /dev/fd/63

$ echo $BASHPID >(cat)
3252 /dev/fd/63

I thought command1 >(command2) is the same as command1 | command2, but in command1 >(command2), each command is inside a different shell, therefore they should have the same output. Where am I wrong?

9

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).

6

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)
5

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)
3
$ echo 1 >(cat > /dev/null)
1 /dev/fd/63
$ echo echo >(cat /dev/null)
echo /dev/fd/63

# We can trace how the commands are executed
# so long as we avoid using shell builtin commands,
# and run the equivalent external program instead, i.e. /usr/bin/echo

$ strace -f -e execve bash -c '/usr/bin/echo >(cat /dev/null)'
execve("/usr/bin/bash", ["bash", "-c", "/usr/bin/echo >(cat /dev/null)"], [/* 56 vars */]) = 0
strace: Process 4213 attached
[pid  4212] execve("/usr/bin/echo", ["/usr/bin/echo", "/dev/fd/63"], [/* 56 vars */]) = 0
strace: Process 4214 attached
[pid  4214] execve("/usr/bin/cat", ["cat", "/dev/null"], [/* 56 vars */]/dev/fd/63
) = 0
[pid  4212] +++ exited with 0 +++
[pid  4214] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=4214, si_uid=1001, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

# Apparently, the order of evaluation is arranged so this works nicely:

$ echo 1 > >(cat > /dev/null)
$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.