Piping the stdout of a command into the stdin of another is a powerful technique. But, what if you need to pipe the stdout of multiple commands? This is where process substitution comes in.
Process substitution feeds the output of a process (or processes) into the stdin of another process.
>(command_list)
<(command_list)
Process substitution uses /dev/fd/<n> files to send the results of the process(es) within parentheses to another process. [1]
|  | There is no space between the the "<" or ">" and the parentheses. Space there would give an error message. | 
|  bash$ echo >(true)
 /dev/fd/63
 
 bash$ echo <(true)
 /dev/fd/63
 
 bash$ echo >(true) <(true)
 /dev/fd/63 /dev/fd/62
 
 
 
 bash$ wc <(cat /usr/share/dict/linux.words)
  483523  483523 4992010 /dev/fd/63
 
 bash$ grep script /usr/share/dict/linux.words | wc
     262     262    3601
 
 bash$ wc <(grep script /usr/share/dict/linux.words)
     262     262    3601 /dev/fd/63
 	       | 
|  | Bash creates a pipe with two file descriptors, --fIn and fOut--. The stdin of true connects to fOut (dup2(fOut, 0)), then Bash passes a /dev/fd/fIn argument to echo. On systems lacking /dev/fd/<n> files, Bash may use temporary files. (Thanks, S.C.) | 
Process substitution can compare the output of two different commands, or even the output of different options to the same command.
|  bash$ comm <(ls -l) <(ls -al)
 total 12
-rw-rw-r--    1 bozo bozo       78 Mar 10 12:58 File0
-rw-rw-r--    1 bozo bozo       42 Mar 10 12:58 File2
-rw-rw-r--    1 bozo bozo      103 Mar 10 12:58 t2.sh
        total 20
        drwxrwxrwx    2 bozo bozo     4096 Mar 10 18:10 .
        drwx------   72 bozo bozo     4096 Mar 10 17:58 ..
        -rw-rw-r--    1 bozo bozo       78 Mar 10 12:58 File0
        -rw-rw-r--    1 bozo bozo       42 Mar 10 12:58 File2
        -rw-rw-r--    1 bozo bozo      103 Mar 10 12:58 t2.sh | 
Process substitution can compare the contents of two directories -- to see which filenames are in one, but not the other.
| 1 diff <(ls $first_directory) <(ls $second_directory) | 
Some other usages and uses of process substitution:
| 1 read -a list < <( od -Ad -w24 -t u2 /dev/urandom ) 2 # Read a list of random numbers from /dev/urandom, 3 #+ process with "od" 4 #+ and feed into stdin of "read" . . . 5 6 # From "insertion-sort.bash" example script. 7 # Courtesy of JuanJo Ciarlante. | 
| 1 PORT=6881 # bittorrent 2 3 # Scan the port to make sure nothing nefarious is going on. 4 netcat -l $PORT | tee>(md5sum ->mydata-orig.md5) | 5 gzip | tee>(md5sum - | sed 's/-$/mydata.lz2/'>mydata-gz.md5)>mydata.gz 6 7 # Check the decompression: 8 gzip -d<mydata.gz | md5sum -c mydata-orig.md5) 9 # The MD5sum of the original checks stdin and detects compression issues. 10 11 # Bill Davidsen contributed this example 12 #+ (with light edits by the ABS Guide author). | 
| 1 cat <(ls -l) 2 # Same as ls -l | cat 3 4 sort -k 9 <(ls -l /bin) <(ls -l /usr/bin) <(ls -l /usr/X11R6/bin) 5 # Lists all the files in the 3 main 'bin' directories, and sorts by filename. 6 # Note that three (count 'em) distinct commands are fed to 'sort'. 7 8 9 diff <(command1) <(command2) # Gives difference in command output. 10 11 tar cf >(bzip2 -c > file.tar.bz2) $directory_name 12 # Calls "tar cf /dev/fd/?? $directory_name", and "bzip2 -c > file.tar.bz2". 13 # 14 # Because of the /dev/fd/<n> system feature, 15 # the pipe between both commands does not need to be named. 16 # 17 # This can be emulated. 18 # 19 bzip2 -c < pipe > file.tar.bz2& 20 tar cf pipe $directory_name 21 rm pipe 22 # or 23 exec 3>&1 24 tar cf /dev/fd/4 $directory_name 4>&1 >&3 3>&- | bzip2 -c > file.tar.bz2 3>&- 25 exec 3>&- 26 27 28 # Thanks, Stéphane Chazelas | 
Here is a method of circumventing the problem of an echo piped to a while-read loop running in a subshell.
Example 23-1. Code block redirection without forking
|    1 #!/bin/bash
   2 # wr-ps.bash: while-read loop with process substitution.
   3 
   4 # This example contributed by Tomas Pospisek.
   5 # (Heavily edited by the ABS Guide author.)
   6 
   7 echo
   8 
   9 echo "random input" | while read i
  10 do
  11   global=3D": Not available outside the loop."
  12   # ... because it runs in a subshell.
  13 done
  14 
  15 echo "\$global (from outside the subprocess) = $global"
  16 # $global (from outside the subprocess) =
  17 
  18 echo; echo "--"; echo
  19 
  20 while read i
  21 do
  22   echo $i
  23   global=3D": Available outside the loop."
  24   # ... because it does NOT run in a subshell.
  25 done < <( echo "random input" )
  26 #    ^ ^
  27 
  28 echo "\$global (using process substitution) = $global"
  29 # Random input
  30 # $global (using process substitution) = 3D: Available outside the loop.
  31 
  32 
  33 echo; echo "##########"; echo
  34 
  35 
  36 
  37 # And likewise . . .
  38 
  39 declare -a inloop
  40 index=0
  41 cat $0 | while read line
  42 do
  43   inloop[$index]="$line"
  44   ((index++))
  45   # It runs in a subshell, so ...
  46 done
  47 echo "OUTPUT = "
  48 echo ${inloop[*]}           # ... nothing echoes.
  49 
  50 
  51 echo; echo "--"; echo
  52 
  53 
  54 declare -a outloop
  55 index=0
  56 while read line
  57 do
  58   outloop[$index]="$line"
  59   ((index++))
  60   # It does NOT run in a subshell, so ...
  61 done < <( cat $0 )
  62 echo "OUTPUT = "
  63 echo ${outloop[*]}          # ... the entire script echoes.
  64 
  65 exit $? | 
Example 23-2. Redirecting the output of process substitution into a loop.
|    1 #!/bin/bash
   2 # psub.bash
   3 
   4 # As inspired by Diego Molina (thanks!).
   5 
   6 declare -a array0
   7 while read
   8 do
   9   array0[${#array0[@]}]="$REPLY"
  10 done < <( sed -e 's/bash/CRASH-BANG!/' $0 | grep bin | awk '{print $1}' )
  11 #  Sets the default 'read' variable, $REPLY, by process substitution,
  12 #+ then copies it into an array.
  13 
  14 echo "${array0[@]}"
  15 
  16 exit $?
  17 
  18 # ====================================== #
  19 
  20 bash psub.bash
  21 
  22 #!/bin/CRASH-BANG! done #!/bin/CRASH-BANG! | 
A reader sent in the following interesting example of process substitution.
| 1 # Script fragment taken from SuSE distribution: 2 3 # --------------------------------------------------------------# 4 while read des what mask iface; do 5 # Some commands ... 6 done < <(route -n) 7 # ^ ^ First < is redirection, second is process substitution. 8 9 # To test it, let's make it do something. 10 while read des what mask iface; do 11 echo $des $what $mask $iface 12 done < <(route -n) 13 14 # Output: 15 # Kernel IP routing table 16 # Destination Gateway Genmask Flags Metric Ref Use Iface 17 # 127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo 18 # --------------------------------------------------------------# 19 20 # As Stéphane Chazelas points out, 21 #+ an easier-to-understand equivalent is: 22 route -n | 23 while read des what mask iface; do # Variables set from output of pipe. 24 echo $des $what $mask $iface 25 done # This yields the same output as above. 26 # However, as Ulrich Gayer points out . . . 27 #+ this simplified equivalent uses a subshell for the while loop, 28 #+ and therefore the variables disappear when the pipe terminates. 29 30 # --------------------------------------------------------------# 31 32 # However, Filip Moritz comments that there is a subtle difference 33 #+ between the above two examples, as the following shows. 34 35 ( 36 route -n | while read x; do ((y++)); done 37 echo $y # $y is still unset 38 39 while read x; do ((y++)); done < <(route -n) 40 echo $y # $y has the number of lines of output of route -n 41 ) 42 43 More generally spoken 44 ( 45 : | x=x 46 # seems to start a subshell like 47 : | ( x=x ) 48 # while 49 x=x < <(:) 50 # does not 51 ) 52 53 # This is useful, when parsing csv and the like. 54 # That is, in effect, what the original SuSE code fragment does. | 
| [1] | This has the same effect as a named pipe (temp file), and, in fact, named pipes were at one time used in process substitution. |