Sed para arquivos gzip

4

Eu tenho um monte de arquivos gz e a versão descompactada deles contém os padrões A e B=1 (estes são certamente em linhas diferentes onde A aparece primeiro).

Eu quero escrever um comando que me dê o conteúdo das linhas onde A está presente e onde B=1 está presente. Ou pelo menos o conteúdo entre A e B=1 inclusive.

Arquivo de entrada1:

..A ...
...
...B=0..
...

Arquivo de entrada2:

..A ...
...
...B=1..
...

Meu comando deve exibir A ....B=1 para o arquivo2 e nada para o arquivo1.

Eu fiz algo assim, mas não está funcionando como esperado:

find . -name \*.gz -print0 | xargs -0 zcat | sed -n -e '/A/,/B=1/p'

Qual é o problema aqui?

    
por cehptr 13.12.2017 / 14:27

4 respostas

4

Vamos ignorar a compressão por enquanto. Você deseja produzir as linhas entre A e B=1 , mas apenas se ambas aparecerem. O sed que você usou não fará isso, já que ele começa a produzir assim que A é visto e não verifica B=1 . Poderíamos usar o buffer de retenção em sed para manter tudo até que B=1 seja encontrado, mas estou mais confortável com awk , então aqui:

$ echo -en 'not this\nA\nthis\nB=1\nnot this\n' | 
  awk '/A/ {save=1} save {data = data $0 ORS} /B=0/ {save=0; data=""}  /B=1/ {save=0; printf "%s", data; data=""} '
A
this
B=1

A regra B=0 lida com blocos que não devem ser impressos.

Em seguida, manipule a compactação e vários arquivos. O find + xargs que você fez funciona, mas se alguns arquivos puderem ter blocos parciais ( A sem B ), concatenar os arquivos juntos causará problemas. Assumindo que não é o caso, podemos apenas colocar o awk no final:

$ find . -name foo\*.gz -print0 | xargs -0 zcat | \
  awk '/A/ {s=1} s {d = d $0 ORS} /B=0/ {s=0; d=""} 
  /B=1/ {s=0; printf "%s", d; d=""} '      

Se precisarmos lidar com blocos parciais, teremos que lidar com cada arquivo separadamente:

$ find . -name foo\*.gz -print0 | xargs -0 sh -c '
  for f; do zcat "$f" |  awk '\''/A/ {s=1} s {d = d $0 ORS} 
    /B=0/ {s=0; d=""} /B=1/ {s=0; printf "%s", d; d=""} '\''; done' sh

A citação é horrível, portanto, o script awk provavelmente deve ter um arquivo próprio.

Ou apenas faça isso no shell (Bash / ksh / zsh):

$ shopt -s globstar    # set -o globstar in ksh
$ for f in **/*.gz ; do zcat "$f" |
  awk '/A/ {s=1} s {d = d $0 ORS} /B=0/ {s=0; d=""} 
  /B=1/ {s=0; printf "%s", d; d=""} ' ; done

Se quiser imprimir apenas as linhas intervenientes (não as linhas A e B=1 ), troque as posições dos blocos /A/ {...} e /B=.../ {...} .

    
por 13.12.2017 / 15:21
2

Certamente NÃO é o melhor caminho, mas funciona para mim:

find -name "*.gz" | xargs zgrep -l A | xargs zgrep -l "B=1" | xargs zcat | sed -n '/A/,/B=1/p

Primeiro, obtemos uma lista de arquivos, depois filtramos os arquivos contendo A, depois filtramos os arquivos que contêm B = 1, os arquivos resultantes são zcat to sed .

Perigo: se um arquivo contiver B = 1 e A, nessa ordem, o conteúdo desse arquivo será gravado no final.

Exemplo:

$ ls /tmp/file*gz
/tmp/filea.gz  /tmp/fileb.gz
$ zcat /tmp/filea.gz
one
two
three
A
four five
six
B=1
seven
eight
nine
$ zcat /tmp/fileb.gz
one
two
three
A
four five
six
B=0
seven
eight
nine
$ find /tmp -type f -name "file*.gz" | xargs zgrep -l A | xargs zgrep -l "B=1" | xargs zcat | sed -n '/A/,/B=1/p'
A
four five
six
B=1
    
por 13.12.2017 / 15:10
1

Se você tem pcregrep e foi criado com suporte a libz, você pode fazer:

pcregrep --include='\.gz$' -rM '(?s)A.*?B=1' .

Exemplo:

$ pcregrep --help | grep zlib
Files whose names end in .gz are read using zlib.
Files whose names end in .bz2 are read using bzlib2.
$ pcregrep --include='\.gz$' -rM '(?s)A.*?B=1' .
./1/2/3/x.gz:AAA
blih
BOB=123
./b.gz:A
blah
B=1
    
por 14.12.2017 / 13:37
0
zcat *.gz | \
sed  's/B=[0-9].*/&\x00/'  | \
grep  -zo 'A.*B=1' | \
sed 's/\x00/\n=====\n/'
  • linha 1 (pode ser substituída pelo comando find)
  • a linha 2 adiciona um nulo após as linhas "B = ..." separar explicitamente os registros
  • linha 3 greps em uma sequência separada de valores nulos, os padrões A ... B = 1
  • linha 4 (se útil) converte null para um separador mais visível
por 14.12.2017 / 13:01