Canalizar para vários arquivos no shell

29

Eu tenho um aplicativo que produzirá uma grande quantidade de dados que não desejo armazenar no disco. O aplicativo emite principalmente dados que não desejo usar, mas um conjunto de informações úteis que devem ser divididas em arquivos separados. Por exemplo, dada a seguinte saída:

JUNK
JUNK
JUNK
JUNK
A 1
JUNK
B 5
C 1
JUNK

Eu poderia executar o aplicativo três vezes da seguinte maneira:

./app | grep A > A.out
./app | grep B > B.out
./app | grep C > C.out

Isso me daria o que eu quero, mas levaria muito tempo. Também não quero despejar todas as saídas em um único arquivo e analisar isso.

Existe alguma maneira de combinar as três operações mostradas acima de forma que eu só precise executar o aplicativo uma vez e ainda assim obter três arquivos de saída separados?

sj755
fonte

Respostas:

78

Se você tem tee

./app | tee >(grep A > A.out) >(grep B > B.out) >(grep C > C.out) > /dev/null

( daqui )

( sobre substituição de processo )

Aurélien Ooms
fonte
4
Impressionante, isso também pode ser renderizado como:./app | tee >(grep A > A.out) >(grep B > B.out) | grep C > C.out
evilsoup
7
Atualmente, essa resposta é a única precisa, dado o título original da pergunta "canal para vários processos".
acelent
3
+1. Essa é a resposta mais aplicável em geral, pois não depende do fato de o comando de filtragem específico ter sido grep.
Ruakh 26/10
1
Concordo que esta é a melhor resposta para a pergunta feita e deve ser marcada assim. Paralelo é outra solução (conforme publicado), mas depois de fazer algumas comparações cronometradas, o exemplo acima é mais eficiente. Se o op envolveu operações altamente intensivas de CPU, como compactação múltipla de arquivos ou conversão múltipla de mp3, sem dúvida a solução paralela deve ser mais eficaz.
AsymLabs
32

Você pode usar awk

./app | awk '/A/{ print > "A.out"}; /B/{ print > "B.out"}; /C/{ print > "C.out"}'
Rahul Patil
fonte
6
O título da pergunta é canal para vários processos ; esta resposta é sobre "canalização" (envio por regex) para vários arquivos . Como essa resposta foi aceita, o título da pergunta deve ser alterado de acordo.
acelent
@PauloMadeira Você está certo. O que você acha que seria um título melhor?
Sj755 26/10
Sugeri uma edição muito pequena "Canalizar para vários arquivos no shell", está com revisão pendente, confira. Eu esperava remover o comentário se ele fosse aceito.
Acelent 26/10/2013
@PauloMadeira - mudei o título. Não viu sua edição, mas você está correto, o uso de processos no título estava incorreto se esta for a resposta aceita.
slm
17

Você também pode usar as habilidades de correspondência de padrões do seu shell :

./app | while read line; do 
     [[ "$line" =~ A ]] && echo $line >> A.out; 
     [[ "$line" =~ B ]] && echo $line >> B.out; 
     [[ "$line" =~ C ]] && echo $line >> C.out; 
 done

Ou até:

./app | while read line; do for foo in A B C; do 
     [[ "$line" =~ "$foo" ]] && echo $line >> "$foo".out; 
  done; done

Uma maneira mais segura de lidar com barras invertidas e linhas começando com -:

./app | while IFS= read -r line; do for foo in A B C; do 
     [[ "$line" =~ "$foo" ]] && printf -- "$line\n" >> "$foo".out; 
  done; done

Como @StephaneChazelas aponta nos comentários, isso não é muito eficiente. A melhor solução é provavelmente a @ AurélienOoms .

terdon
fonte
Isso pressupõe a entrada não contém barras invertidas ou espaços em branco ou caracteres curinga, ou linhas que começam com -n, -e... Ele também vai ser terrivelmente ineficiente, já que significa várias chamadas do sistema por linha (um read(2)por personagem, o arquivo ser aberto, escrita fechado para cada linha ...). Geralmente, usar while readloops para processar texto em shells é uma prática ruim.
Stéphane Chazelas 27/10/2013
@StephaneChazelas Eu editei minha resposta. Agora deve funcionar com barras invertidas e -netc. Tanto quanto eu posso dizer, ambas as versões funcionam bem com espaços em branco, estou errado?
terdon
Não, o primeiro argumento para printfé o formato. Não há razão para deixar suas variáveis ​​sem aspas lá.
Stéphane Chazelas 27/10/2013
Isso também será interrompido no bash (e outros shells que usam cstrings de maneira semelhante) se houver nulos na entrada.
Chris Baixo
9

Se você possui vários núcleos e deseja que os processos sejam paralelos, é possível:

parallel -j 3 -- './app | grep A > A.out' './app | grep B > B.out' './app | grep C > C.out'

Isso gerará três processos em núcleos paralelos. Se você deseja que haja alguma saída para o console ou um arquivo mestre, ele tem a vantagem de manter a saída em alguma ordem, em vez de misturá-la.

O utilitário gnu paralelo de Ole Tange pode ser obtido na maioria dos repositórios com o nome paralelo ou moreutils . A fonte pode ser obtida em Savannah.gnu.org . Também um vídeo instrutivo introdutório está aqui .

Termo aditivo

Usando a versão mais recente do paralelo (não necessariamente a versão em seu repositório de distribuição), você pode usar a construção mais elegante:

./app | parallel -j3 -k --pipe 'grep {1} >> {1}.log' ::: 'A' 'B' 'C'

O que alcança o resultado da execução de um ./app e 3 processos grep paralelos em núcleos ou encadeamentos separados (conforme determinado pelo próprio paralelo, também considera o -j3 opcional, mas é fornecido neste exemplo para fins instrutivos).

A versão mais recente do paralelo pode ser obtida fazendo:

wget http://ftpmirror.gnu.org/parallel/parallel-20131022.tar.bz2

Em seguida, descompacte o arquivo usual, cd to parallel- {date}, ./configure && make, sudo make install. Isso instalará paralelo, página de manual paralela e página de manual paralela_tutorial.

AsymLabs
fonte
7

Aqui está um em Perl:

./app | perl -ne 'BEGIN {open(FDA, ">A.out") and 
                         open(FDB, ">B.out") and 
                         open(FDC, ">C.out") or die("Cannot open files: $!\n")} 
                  print FDA $_ if /A/; print FDB $_ if /B/; print FDC $_ if /C/'
troydj
fonte
1
sed -ne/A/w\ A.out -e/B/w\ B.out -e/C/p <in >C.out

... se <infor legível, todos os três arquivos serão truncados antes que algo lhes seja gravado.

mikeserv
fonte