Substituição de processo e canal

Gostaria de 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, a substituição do processo pode fazer tudo o que o pipe pode fazer?

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

Resposta

Uma boa maneira de entender o A diferença entre eles é fazer alguns experimentos na linha de comando. Apesar da semelhança visual no uso do caractere <, ele faz algo muito diferente do que um redirecionamento ou barra vertical.

Vamos usar o date comando para teste.

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

Este é um exemplo inútil, mas mostra que cat aceitou a saída de date em STDIN e cuspiu de volta. Os mesmos resultados podem ser alcançados por substituição de 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 stream STDIN, cat recebeu o nome de um arquivo que precisava ser aberto e ler. Você pode ver esta etapa usando echo em vez de cat.

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

Quando cat 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 

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

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

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

$ echo < <(date) <blank> 

Visto que echo não lê STDIN e nenhum argumento foi passado, não recebemos nada.

Pipes e redirecionamentos de entrada empurram 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, em seguida, passa esse nome de arquivo no lugar do comando. Qualquer comando que você está usando o trata como um nome de arquivo. Observe que o arquivo criado não é um arquivo normal, mas um canal nomeado que é removido automaticamente quando não é mais necessário.

Comentários

  • Se eu entendido corretamente, tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 diz que a substituição de processo cria arquivos temporários, não canais nomeados. Tanto quanto eu sei o nome não cria arquivos temporários. Gravar no pipe nunca envolve gravar no disco: stackoverflow.com/a/6977599/788700
  • Eu sei que esta resposta é legítima ‘ porque usa a palavra grok : D
  • @Adobe, você pode confirmar se o arquivo temporário que a substituição do processo produz é um tubo nomeado com: [[ -p <(date) ]] && echo true. Isso produz true quando o executo com o bash 4.4 ou 3.2.

Resposta

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

Entradas múltiplas do processo

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

Lá simplesmente não há maneira de fazer isso com tubos.

Preservando 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, então outra entrada é impossível.

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

Mas dessa forma funciona perfeitamente.

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

Substituição do processo de saída

Observe também que a substituição do processo também funciona de outra maneira. Portanto, 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 tanto complicado, mas envia stdout para /dev/null, enquanto canaliza stderr para um script sed para extrair os nomes dos arquivos para os quais uma ” Permissão negada ” o erro foi exibido e, em seguida, envia ESSES resultados para um arquivo.

Observe que o primeiro comando e o redirecionamento stdout está entre parênteses ( subshell ) para que apenas o resultado desse comando seja enviado para /dev/null e não interfira com o resto da linha.

Comentários

  • É ‘ é importante notar que no diff exemplo, você pode querer se preocupar com o caso em que cd pode falhar: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
  • ” enquanto canaliza stderr “: isn ‘ t o apontar que isso não está encanando, mas passando por um arquivo fifo?
  • @Gauthier no; o comando é substituído não por um fifo, mas por uma referência ao descritor de arquivo. Portanto, ” echo < (echo) ” deve produzir algo como ” / dev / fd / 63 “, que é um dispositivo de caractere especial que lê ou escreve do FD número 63.

Resposta

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

bash relatórios de página de manual:

Substituição de processo
A substituição de processo é suportada em sistemas que suportam pipes nomeados (FIFOs) ou o método / dev / fd de nomear arquivos abertos. Tem a forma de < (lista) ou> (lista). A lista de processos é executada com sua entrada ou saída conectada a um FIFO ou algum arquivo em / dev / fd. O nome deste arquivo é passado como um 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 a forma < (lista) for usada, o arquivo passado como um argumento deve ser lido para obter a saída da lista.

Quando disponível, substituição de processo é executado simultaneamente com a 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 processos e os canais não têm muito em comum.

Se quiser canalizar em sequência a saída de vários comandos, você pode usar 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 command3 aceitar um parâmetro de arquivo (em substituição de stdin )

command3 <(command1; command2) 

Comentários

  • so < ( ) e < < () tem o mesmo efeito, certo?
  • @solfish: não exatamente: a primeira pode ser usado sempre que um nome de arquivo é esperado, o segundo é um redirecionamento de entrada para esse nome de arquivo

Resposta

Se um comando pega uma lista de arquivos como argumentos e processa esses arquivos como entrada (ou saída, mas não comumente), cada um desses arquivos pode ser um pipe nomeado ou pseudo-arquivo / dev / fd fornecido de forma transparente pela substituição do processo:

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

Este irá “canalizar” a saída dos três comandos para classificar, já que classificar pode pegar uma lista de arquivos de entrada na linha de comando.

Comentários

  • IIRC a < (comando) sintaxe é um recurso somente bash.
  • @Philomath: É ‘ em ZSH também.
  • Bem, ZSH tem tudo … (ou pelo menos tenta).
  • @Philomath: Como a substituição de processos é implementada em outros shells?
  • @Philomath <(), como muitos recursos avançados do shell, era originalmente um recurso ksh e foi adotado por bash e zsh. psub é especificamente um recurso de peixe, nada a ver com POSIX.

Resposta

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

Para o date | cat exemplo acima, um comando que usa a substituição de processo do forma >(command) para obter o mesmo efeito seria,

date > >(cat) 

Observe que > antes de >(cat) ser necessário. Isso pode ser claramente ilustrado por echo como na resposta de @Caleb “.

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

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

Suponha que você tenha um programa que aceita apenas nomes de arquivos como parâmetros e não processa stdin ou stdout.Usarei o script simplificado psub.sh para 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 argumentos são arquivos (não necessariamente regulares arquivos) e se este for o caso, escreva o primeiro campo de cada linha de "$1" em "$2" usando 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 do 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.sh também lê stdin além do que é mencionado acima, então tal forma equivalente não existe e, nesse caso, não há nada que possamos usar em vez da substituição do processo (é claro que você também pode usar um pipe nomeado ou arquivo temporário, mas isso é outra história).

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *