de uma forma simples:
quando vc faz
bash$ foo | bar
vc executa o programa foo no contexto atual do shell, e o bar é executo em um subshell, em outro processo, filho do processo atual. quando vc faz isso vc precisa lembrar que todas as variaveis de ambiente do shell corrente podem não estar presentes pro subshell ( vc precisa exportar as variaveis ) e todas as alterações de variaveis de ambiente no subshell "não voltam".
isso é particularmente importante se vc tem variaveis que mudam o comportamento dos programas envolvidos como PATH, LC_ALL, etc.
outra coisa é que vc criou um pipe entre os processos e bar só lê quando foo escreve, então vc sincronizou os processos. se foo parar de escrever e terminar o, bar vai receber um SIGPIPE.
melhor exemplo
bash$ head arquivo_enorrrrrrrme.txt | grep palavra
head nesse contexto vai mandar as primeiras 10 linhas e vai morrer. o grep só vai recever as 10 primeiras linhas para trabalhar e depois vai terminar apos receber o SIGPIPE.
portanto vc tem que pensar se vc quer essas duas coisas.
AH! muito importante, vc supoe que bar esta lendo da STDIN.
Agora, quando vc faz
bash$ bar <( foo )
bar vai recever um link para um named pipe, e não vai ler da STDIN pq vc esta passando um arquivo. vc pode fazer algo como
bash$ paste <( cat arquivo1 ) <( cat arquivo2 )
que vc não consegue fazer com pipes "ordinarios" ( ou pelo menos sem muita magia negra ).
uma outra vantagem de fazer isso é que vc executa bar no shell corrente. não precisa se preocupar em exportar nada.
E vc pode fazer coisas como:
bash$ i=0
bash$ while read line ; do echo $line ; ((i++)); done < <(ls)
...
arquivos
...
bash$ echo $i
18
enquanto isso:
bash$ i=0
bash$ ls | while read line ; do echo $line ; ((i++)); done
...
arquivos
...
bash$ echo $i
0
nesse caso $i é 0 pois vc incrementou no subshell e baubau.
em algumas situações vai ser importante vc saber a diferença e escolher certo.