Substituição de processo e tubulação

86

Eu queria saber como entender o seguinte:

Canalizar o stdout de um comando para o stdin de outro é uma técnica poderosa. Mas e se você precisar canalizar o stdout de vários comandos? É aqui que entra a substituição do processo.

Em outras palavras, o processo de substituição pode fazer o que o pipe pode fazer?

O que a substituição de processo pode fazer, mas o pipe não pode?

Tim
fonte

Respostas:

134

Uma boa maneira de entender a diferença entre eles é experimentar um pouco na linha de comando. Apesar da semelhança visual no uso do <personagem, ele faz algo muito diferente de um redirecionamento ou canal.

Vamos usar o datecomando para testar.

$ date | cat
Thu Jul 21 12:39:18 EEST 2011

Este é um exemplo inútil, mas mostra que cataceitou a saída de dateno STDIN e cuspiu de volta. Os mesmos resultados podem ser alcançados pela substituição do processo:

$ cat <(date)
Thu Jul 21 12:40:53 EEST 2011

No entanto, o que aconteceu nos bastidores foi diferente. Em vez de receber um fluxo STDIN, catfoi realmente transmitido o nome de um arquivo que ele precisava para abrir e ler. Você pode ver esta etapa usando em echovez de cat.

$ echo <(date)
/proc/self/fd/11

Quando o gato recebeu o nome do arquivo, ele leu o conteúdo do arquivo para nós. Por outro lado, echo apenas nos mostrou o nome do arquivo que foi passado. Essa diferença se torna mais óbvia se você adicionar mais substituições:

$ cat <(date) <(date) <(date)
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011

$ echo <(date) <(date) <(date)
/proc/self/fd/11 /proc/self/fd/12 /proc/self/fd/13

É possível combinar a substituição do processo (que gera um arquivo) e o redirecionamento de entrada (que conecta um arquivo ao STDIN):

$ cat < <(date)
Thu Jul 21 12:46:22 EEST 2011

Parece praticamente o mesmo, mas desta vez o gato recebeu o fluxo STDIN em vez de um nome de arquivo. Você pode ver isso tentando com o eco:

$ echo < <(date)
<blank>

Como o eco não lê STDIN e nenhum argumento foi passado, não obtemos nada.

Canais e redirecionamentos de entrada direcionam o conteúdo para o fluxo STDIN. A substituição de processo executa os comandos, salva sua saída em um arquivo temporário especial e passa o nome do arquivo no lugar do comando. Qualquer que seja o comando que você esteja usando, trata-o como um nome de arquivo. Observe que o arquivo criado não é um arquivo comum, mas um canal nomeado que é removido automaticamente quando não é mais necessário.

Caleb
fonte
Se eu entendi corretamente, tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 diz que a substituição de processo cria arquivos temporários, não denominados pipes. Tanto quanto eu sei nomeado, não crie arquivos temporários. Escrevendo para o tubo não envolve a escrita para o disco: stackoverflow.com/a/6977599/788700
Adobe
Eu sei que esta resposta é legítima, porque usa a palavra grok : D
aqn 1/11
2
@Adobe você pode confirmar se a substituição processo arquivo temporário produz é um pipe nomeado com: [[ -p <(date) ]] && echo true. Isso produz truequando eu o executo com o bash 4.4 ou 3.2.
De Novo
24

Suponho que você esteja falando bashou sobre algum outro shell avançado, porque o shell posix não possui substituição de processo .

bash relatórios da página de manual:

Substituição de
Processo A substituição de processo é suportada em sistemas que suportam FIFOs (pipes nomeados) ou o método / dev / fd para nomear arquivos abertos. Assume a forma de <(lista) ou> (lista). A lista de processos é executada com sua entrada ou saída conectada a um FIFO ou a algum arquivo em / dev / fd. O nome desse arquivo é passado como argumento para o comando atual como resultado da expansão. Se o formulário> (lista) for usado, a gravação no arquivo fornecerá entrada para a lista. Se o formulário <(lista) for usado, o arquivo passado como argumento deve ser lido para obter a saída da lista.

Quando disponível, a substituição do processo é realizada simultaneamente com expansão de parâmetro e variável, substituição de comando e expansão aritmética.

Em outras palavras, e de um ponto de vista prático, você pode usar uma expressão como a seguinte

<(commands)

como um nome de arquivo para outros comandos que requerem um arquivo como parâmetro. Ou você pode usar o redirecionamento para esse arquivo:

while read line; do something; done < <(commands)

Voltando à sua pergunta, parece-me que a substituição de processo e os tubos não têm muito em comum.

Se você deseja canalizar em seqüência a saída de vários comandos, use um dos seguintes formulários:

(command1; command2) | command3
{ command1; command2; } | command3

mas você também pode usar o redirecionamento na substituição do processo

command3 < <(command1; command2)

finalmente, se command3aceita um parâmetro de arquivo (em substituição de stdin)

command3 <(command1; command2)
enzotib
fonte
então <() e <<() fazem o mesmo efeito, certo?
solfish
@solfish: não exacllty: o firse pode ser usado sempre que um nome de arquivo é esperado, o segundo é um redirecionamento de entrada para esse arquivo
enzotib
23

Aqui estão três coisas que você pode fazer com a substituição do processo que são impossíveis de outra maneira.

Várias entradas de processo

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)

Simplesmente não há como fazer isso com tubos.

Preservando o STDIN

Digamos que você tenha o seguinte:

curl -o - http://example.com/script.sh
   #/bin/bash
   read LINE
   echo "You said ${LINE}!"

E você deseja executá-lo diretamente. O seguinte falha miseravelmente. O Bash já está usando STDIN para ler o script, portanto, outras entradas são impossíveis.

curl -o - http://example.com/script.sh | bash 

Mas dessa maneira funciona perfeitamente.

bash <(curl -o - http://example.com/script.sh)

Substituição do processo de saída

Observe também que a substituição de processo também funciona da outra maneira. Então você pode fazer algo assim:

(ls /proc/*/exe >/dev/null) 2> >(sed -n \
  '/Permission denied/ s/.*\(\/proc.*\):.*/\1/p' > denied.txt )

Esse é um exemplo um pouco complicado, mas ele envia stdout para /dev/null, enquanto canaliza stderr para um script sed para extrair os nomes dos arquivos para os quais um erro "Permissão negada" foi exibido e, em seguida, envia OS resultados para um arquivo.

Observe que o primeiro comando e o redirecionamento stdout estão entre parênteses ( subshell ), para que apenas o resultado do comando THAT seja enviado para /dev/nulle não mexa com o restante da linha.

tylerl
fonte
É importante notar que, no diffexemplo, você pode querer se preocupar com o caso em que o cdpode falhar: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
Php 6/16
"while piping stderr": não é o ponto que isso não é canalização, mas está passando por um arquivo fifo?
Gauthier
@Gauthier no; o comando é substituído não por um fifo, mas por uma referência ao descritor de arquivo. Assim, "echo <(eco)" deve render algo como "/ dev / fd / 63", que é um dispositivo de caractere especial que lê ou escreve a partir de FD número 63.
tylerl
10

Se um comando pegar uma lista de arquivos como argumentos e processá-los como entrada (ou saída, mas não comumente), cada um desses arquivos poderá ser um pipe nomeado ou pseudo-arquivo / dev / fd fornecido de forma transparente pela subscrição do processo:

$ sort -m <(command1) <(command2) <(command3)

Isso irá "canalizar" a saída dos três comandos para classificar, pois a classificação pode levar uma lista de arquivos de entrada na linha de comando.

camh
fonte
1
IIRC, a sintaxe <(comando) é um recurso somente do bash.
Philomath
@ Philomath: Está no ZSH também.
Caleb
Bem, o ZSH tem tudo ... (ou pelo menos tenta).
Philomath 21/07
@ Philomath: Como a substituição de processo é implementada em outros shells?
Camh
4
O @ Philomath <(), como muitos recursos avançados do shell, era originalmente um recurso do ksh e foi adotado pelo bash e pelo zsh. psubé especificamente um recurso de peixe, nada a ver com o POSIX.
Gilles
3

Deve-se observar que a substituição do processo não se limita ao formulário <(command), que usa a saída commandcomo um arquivo. Pode estar no formato >(command)que alimenta um arquivo como entrada commandtambém. Isso também é mencionado na citação do manual do bash na resposta do @ enzotib.

Para o date | catexemplo acima, um comando que usa a substituição de processo do formulário >(command)para obter o mesmo efeito seria,

date > >(cat)

Observe que o >antes >(cat)é necessário. Isso pode ser novamente ilustrado claramente echocomo na resposta de @ Caleb.

$ echo >(cat)
/dev/fd/63

Portanto, sem o extra >, date >(cat)seria o mesmo date /dev/fd/63que imprimirá uma mensagem para stderr.

Suponha que você tenha um programa que apenas use nomes de arquivos como parâmetros e não processe stdinou stdout. Usarei o script simplificado demais psub.shpara ilustrar isso. O conteúdo de psub.shé

#!/bin/bash
[ -e "$1" -a -e "$2" ] && awk '{print $1}' "$1" > "$2"

Basicamente, ele testa se ambos os seus argumentos são arquivos (não necessariamente arquivos regulares) e, se esse for o caso, escreva o primeiro campo de cada linha "$1"para "$2"usar o awk. Então, um comando que combina tudo o que foi mencionado até agora é,

./psub.sh <(printf "a a\nc c\nb b") >(sort)

Isso imprimirá

a
b
c

e é equivalente a

printf "a a\nc c\nb b" | awk '{print $1}' | sort

mas o seguinte não funcionará, e temos que usar a substituição de processo aqui,

printf "a a\nc c\nb b" | ./psub.sh | sort

ou sua forma equivalente

printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort

Se ./psub.shtambém ler stdinalém do mencionado acima, esse formulário equivalente não existe e, nesse caso, não podemos usar nada em vez da substituição do processo (é claro que você também pode usar um pipe nomeado ou um arquivo temporário, mas isso é outro história).

Weijun Zhou
fonte