Como você pode diferenciar dois pipelines no Bash?


143

Como você pode diferenciar dois pipelines sem usar arquivos temporários no Bash? Digamos que você tenha dois pipelines de comando:

foo | bar
baz | quux

E você quer encontrar o diffresultado deles. Uma solução seria obviamente:

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b

É possível fazer isso sem o uso de arquivos temporários no Bash? Você pode se livrar de um arquivo temporário canalizando um dos pipelines para diferenciar:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -

Mas você não pode canalizar os dois pipelines no diff simultaneamente (não de maneira óbvia, pelo menos). Existe algum truque inteligente /dev/fdpara fazer isso sem usar arquivos temporários?

Respostas:


146

Uma linha com 2 arquivos tmp (não o que você deseja) seria:

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt

Com o bash , você pode tentar:

 diff <(foo | bar) <(baz | quux)

 foo | bar | diff - <(baz | quux)  # or only use process substitution once

A 2ª versão lembrará mais claramente qual entrada foi qual, mostrando
-- /dev/stdinvs. ++ /dev/fd/63ou algo assim, em vez de dois fds numerados.


Nem mesmo um pipe nomeado aparecerá no sistema de arquivos, pelo menos nos sistemas operacionais em que o bash pode implementar a substituição do processo usando nomes de arquivos como /dev/fd/63obter um nome de arquivo que o comando possa abrir e ler para ler de fato um descritor de arquivo já aberto que o bash define antes de executar o comando. (ou seja, o bash usa pipe(2)antes do fork e, em seguida, dup2redireciona da saída de quuxpara um descritor de arquivo de entrada para diff, no fd 63.)

Em um sistema sem "mágico" /dev/fdou /proc/self/fd, o bash pode usar pipes nomeados para implementar a substituição do processo, mas pelo menos os gerenciaria, diferentemente dos arquivos temporários, e seus dados não seriam gravados no sistema de arquivos.

Você pode verificar como o bash implementa a substituição do processo echo <(true)para imprimir o nome do arquivo em vez de lê-lo. Imprime /dev/fd/63em um sistema Linux típico. Ou, para obter mais detalhes sobre exatamente o que o sistema chama o bash usa, esse comando em um sistema Linux rastreia as chamadas de sistema de descritores de arquivos e arquivos.

strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'

Sem o bash, você poderia fazer um pipe nomeado . Use -para dizer diffpara ler uma entrada de STDIN e use o pipe nomeado como o outro:

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt

Observe que você pode canalizar apenas uma saída para várias entradas com o comando tee:

ls *.txt | tee /dev/tty txtlist.txt 

O comando acima exibe a saída de ls * .txt no terminal e a envia para o arquivo de texto txtlist.txt.

Mas com a substituição do processo, você pode usar teepara alimentar os mesmos dados em vários pipelines:

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar

5
mesmo sem bash, você pode usar temporária FIFOmkfifo a; cmd >a& cmd2|diff a -; rm a
unhammer

Você pode usar um tubo regular para um dos argumentos: pipeline1 | diff -u - <(pipeline2). Em seguida, a saída lembrará mais claramente qual entrada foi qual, mostrando -- /dev/stdinvs. ++ /dev/fd/67ou algo assim, em vez de dois fds numerados.
Peter Cordes

O processo substitution ( foo <( pipe )) não modifica o sistema de arquivos. O cano é anônimo ; não tem nome no sistema de arquivos . O shell usa a pipechamada do sistema para criá-lo, não mkfifo. Use strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'para rastrear chamadas de sistema de descritores de arquivos e arquivos, se desejar ver por si mesmo. No Linux, /dev/fd/63faz parte do /procsistema de arquivos virtual; ele possui entradas automaticamente para cada descritor de arquivo e não é uma cópia do conteúdo. Então você não pode chamar isso de um "arquivo temporário", a menos que foo 3<bar.txta contagem
Peter Cordes

@PeterCordes Bons pontos. Incluímos seu comentário na resposta para obter mais visibilidade.
VonC

1
@ PeterCordes Deixarei qualquer edição para você: é isso que torna o Stack Overflow interessante: qualquer um pode "corrigir" uma resposta.
VonC

127

No bash, você pode usar subshells, para executar os pipelines de comando individualmente, colocando o pipeline entre parênteses. Você pode prefixá-los com <para criar pipes nomeados anônimos que podem ser passados ​​para diff.

Por exemplo:

diff <(foo | bar) <(baz | quux)

Os pipes nomeados anônimos são gerenciados pelo bash, para que sejam criados e destruídos automaticamente (ao contrário dos arquivos temporários).


1
Muito mais detalhado do que minha redação na mesma solução - lote anônimo -. +1
VonC 6/08/08

4
Isso é chamado de substituição de processo no Bash.
Franklin Yu

5

Algumas pessoas que chegam a esta página podem estar procurando um diff linha por linha, para o qual commou grep -fdeve ser usado.

Uma coisa a salientar é que, em todos os exemplos da resposta, as diferenças não serão iniciadas até que ambos os fluxos tenham terminado. Teste isso com, por exemplo:

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)

Se esse é um problema, você pode tentar sd (stream diff), que não requer classificação (como o commfaz) nem substituição de processo, como nos exemplos acima, é uma ordem ou magnitude mais rápida que grep -f e suporta fluxos infinitos.

O exemplo de teste que proponho seria escrito assim em sd:

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'

Mas a diferença é que isso seq 100seria diferente seq 10imediatamente. Observe que, se um dos fluxos é a tail -f, o diff não pode ser feito com a substituição do processo.

Aqui está um post que escrevi sobre diferentes fluxos no terminal, que é apresentado sd.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.