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.
A command substitution ($(...)
) will be replaced by the output of the command, while a process substitution (<(...)
) will be replaced by a filename from which the output of the command may be read. The command, in both instances, will be run in a subshell.
In your case, the output from pwd
in <(pwd)
may be found at /dev/fd/63
. This file ceases to exist as soon as the command that uses the process substitution has finished executing (when the assignment to var
in your example is done).
The filename returned by a process substitution is the name of a file descriptor or named pipe, not a regular file:
Process substitution is supported on systems that
support named pipes (FIFOs) or the /dev/fd
method of naming open files.
A common use of process substitution is to pre-sort files for the join
command:
$ join <( sort file1 ) <( sort file2 )
or for removing columns from a file (here, column 2 is removed from a tab-delimited file by using cut
twice and paste
to stitch the result together):
$ paste <( cut -f 1 file ) <( cut -f 3- file )
Process substitution is more or less a syntactical shortcut for avoiding using temporary files explicitly.
Both command substitutions and process substitutions are performed in subshells. The following shows that the environment in these subshells do not affect the parent shell's environment:
$ unset t
$ echo "$( t=1234; echo "$t" )"
1234
$ echo "$t"
(empty line output)
Here, echo
gets 1234
as a string argument from the command substitution.
$ unset t
$ cat <( t=4321; echo "$t" )
4321
$ echo "$t"
(empty line output)
Here, cat
get the filename of a file (named pipe/file descriptor) as its argument. The file contains the data 4321
.
Best Answer
The key here is that opening a FIFO is a blocking operation. The
open
only returns once both ends are connected, i.e. once the fifo is open for both reading and writing.man fifo(7)
In the 1st case, the shell forks to execute the pipeline, so opening the fifo for reading (
cat fifo
) and opening the fifo for writing (> fifo
) happen in separate processes, so happen independently.In the 2nd case, the open for reading and open for writing (
3<>fifo
) happen in a single step.In the 3rd case,
<(cat fifo)
expands to a filename, e.g./dev/fd/42
. So it's like you're runningnc -l localhost 8888 /dev/fd/42 > fifo
. You need an extra<
for it to be equivalent, e.g.nc -l localhost 8888 < <(cat fifo) > fifo
.In the 4th case, the shell is trying to open the fifo for reading (
< fifo
) and open it for writing (> fifo
) as part of the same process. The shell does them one at a time, left to right. So it tries to openfifo
for reading, and blocks forever, waiting for something to openfifo
for writing. I think you'll find that in this case,nc
never even got started, and the port was never opened for listening.