Pipe para vários arquivos no shell

29

Eu tenho uma aplicação que irá produzir uma grande quantidade de dados que eu não quero armazenar no disco. A aplicação produz principalmente dados que eu 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 forma:

./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. Eu também não quero despejar todas as saídas para um único arquivo e analisá-lo.

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

    
por sj755 26.10.2013 / 19:03

6 respostas

78

Se você tiver tee

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

(de aqui )

( sobre substituição de processos )

    
por 26.10.2013 / 20:05
32

Você pode usar awk

./app | awk '/A/{ print > "A.out"}; /B/{ print > "B.out"}; /C/{ print > "C.out"}'
    
por 26.10.2013 / 19:11
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é mesmo:

./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 que começam 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 aponta @StephaneChazelas nos comentários, isso não é muito eficiente. A melhor solução é provavelmente @ AurélienOoms '.

    
por 26.10.2013 / 19:26
9

Se você tiver vários núcleos e quiser que os processos estejam em paralelo, faça o seguinte:

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ê quiser que haja alguma saída para o console ou um arquivo mestre, ele terá a vantagem de manter a saída em alguma ordem, em vez de misturá-la.

O utilitário gnu paralelo do Ole Tange pode ser obtido a partir da maioria dos repos com o nome paralelo ou moreutils . A fonte pode ser obtida em Savannah.gnu.org . Além disso, um vídeo instrutivo introdutório é aqui .

Adendo

Usando a versão mais recente do paralela (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'

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 paralelismo, também considere o -j3 como opcional, mas é fornecido neste exemplo para propósitos instrutivos) .

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

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

Em seguida, o habitual descompactar, cd para parallel- {date}, ./configure & & make, sudo make install. Isso instalará o paralelismo, a página man paralela e a página man parallel_tutorial.

    
por 27.10.2013 / 11:31
7

Aqui está uma 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/'
    
por 26.10.2013 / 19:48
1
sed -ne/A/w\ A.out -e/B/w\ B.out -e/C/p <in >C.out

... se <in for legível, todos os três arquivos serão truncados antes que algo seja escrito para eles.

    
por 09.12.2015 / 17:54