Process Substitution – Difference Between Subshells and Process Substitution

processprocess-substitutionsubshell

In bash, I want to assign my current working directory to a variable. Using a subshell, I can do this.

var=$(pwd)

echo $var
/home/user.name

If I use process substitution like so:

var=<(pwd)

echo $var
/dev/fd/63

I have understood that process substitution is primarily used when a program does not accept STDIN. It is unclear to me what a process substitution exactly does and why it assigns /dev/fd/63 to var.

Best Answer

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.