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
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 quecd
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).
[[ -p <(date) ]] && echo true
. Isso produztrue
quando o executo com o bash 4.4 ou 3.2.