Doing same strace
, you can see the differences:
With pipe
:
$ strace -c ./pipe.sh
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
57.89 0.103005 5 20000 clone
40.81 0.072616 2 30000 10000 wait4
0.58 0.001037 0 120008 rt_sigprocmask
0.40 0.000711 0 10000 pipe
With proc-sub
:
$ strace -c ./procsub.sh
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
85.08 0.045502 5 10000 clone
3.25 0.001736 0 90329 322 read
2.12 0.001133 0 20009 open
2.03 0.001086 0 50001 dup2
With above statistics, you can see pipe
create more child processes (clone
syscall) and spending many times to wait child process (wait4
syscall) to finish for parent process to continue executing.
Process substitution
is not. It can read directly from child processes. Process substitution
is performed at the same time with parameter and variable expansion, the command in Process Substitution
run in background. From bash manpage
:
Process Substitution
Process substitution is supported on systems that support named pipes
(FIFOs) or the /dev/fd method of naming open files. It takes the form
of <(list) or >(list). The process list is run with its input or out‐
put connected to a FIFO or some file in /dev/fd. The name of this file
is passed as an argument to the current command as the result of the
expansion. If the >(list) form is used, writing to the file will pro‐
vide input for list. If the <(list) form is used, the file passed as
an argument should be read to obtain the output of list.
When available, process substitution is performed simultaneously with
parameter and variable expansion, command substitution, and arithmetic
expansion.
Update
Doing strace with statistics from child processes:
With pipe
:
$ strace -fqc ./pipe.sh
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
70.76 0.215739 7 30000 10000 wait4
28.04 0.085490 4 20000 clone
0.78 0.002374 0 220008 rt_sigprocmask
0.17 0.000516 0 110009 20000 close
0.15 0.000456 0 10000 pipe
With proc-sub
:
$ strace -fqc ./procsub.sh
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
52.38 0.033977 3 10000 clone
32.24 0.020913 0 96070 6063 read
5.24 0.003398 0 20009 open
2.34 0.001521 0 110003 10001 fcntl
1.87 0.001210 0 100009 close
You were really close:
tr "o" "a" < <(echo "Foo")
The substitution <()
makes a file descriptor and just pastes the path to the shell. For comprehension just execute:
<(echo blubb)
You will see the error:
-bash: /dev/fd/63: Permission denied
That's why it just pastes /dev/fd/63
into the shell and /dev/fd/63
is not excutable, because it's a simple pipe. In the tr
-example above, it's echo "Foo"
that writes to the pipe and via input redirection <
it's the tr
command that reads from the file descriptor.
Best Answer
A good way to grok the difference between them is to do a little experimenting on the command line. In spite of the visual similarity in use of the
<
character, it does something very different than a redirect or pipe.Let's use the
date
command for testing.This is a pointless example but it shows that
cat
accepted the output ofdate
on STDIN and spit it back out. The same results can be achieved by process substitution:However what just happened behind the scenes was different. Instead of being given a STDIN stream,
cat
was actually passed the name of a file that it needed to go open and read. You can see this step by usingecho
instead ofcat
.When cat received the file name, it read the file's content for us. On the other hand, echo just showed us the file's name that it was passed. This difference becomes more obvious if you add more substitutions:
It is possible to combine process substitution (which generates a file) and input redirection (which connects a file to STDIN):
It looks pretty much the same but this time cat was passed STDIN stream instead of a file name. You can see this by trying it with echo:
Since echo doesn't read STDIN and no argument was passed, we get nothing.
Pipes and input redirects shove content onto the STDIN stream. Process substitution runs the commands, saves their output to a special temporary file and then passes that file name in place of the command. Whatever command you are using treats it as a file name. Note that the file created is not a regular file but a named pipe that gets removed automatically once it is no longer needed.