compare o arquivo B com A e extraia dados de A usando awk, sed ou grep

3

Eu tenho dois arquivos, o arquivo A, que contém todos os dados, enquanto há outro arquivo B, que tem apenas os IDs que eu quero é comparar o arquivo B com o arquivo A e recuperar os dados que estão presentes nos ID's. Estou usando o Suse Linux.

arquivo A

C    02020 Two-component system [PATH:aap02020]
D      NT05HA_1798 sensor protein CpxA  
D      NT05HA_1797 CpxR K07662 cpxR
C    02030 *Bacterial chemotaxis* [PATH:aap02030]
D      NT05HA_0919 maltose-binding periplasmic protein
D      NT05HA_0918 maltose-binding periplasmic protein 
C    03070 *Bacterial secretion system* [PATH:aap03070]
D      NT05HA_1309 protein-export membrane protein SecD 
D      NT05HA_1310 protein-export membrane protein SecF 
D      NT05HA_1819 preprotein translocase subunit SecE
D      NT05HA_1287 protein-export membrane protein  
C    02060 Phosphotransferase system (PTS) [PATH:aap02060]
D      NT05HA_0618 phosphoenolpyruvate-protein 
D      NT05HA_0617 phosphocarrier protein HPr 
D      NT05HA_0619 pts system 

arquivo B

Bacterial chemotaxis
Bacterial secretion system

Saída desejada:

C    02030 *Bacterial chemotaxis* [PATH:aap02030]
D      NT05HA_0919 maltose-binding periplasmic protein
D      NT05HA_0918 maltose-binding periplasmic protein 
C    03070 *Bacterial secretion system* [PATH:aap03070]
D      NT05HA_1309 protein-export membrane protein SecD
D      NT05HA_1310 protein-export membrane protein SecF
D      NT05HA_1819 preprotein translocase subunit SecE  
D      NT05HA_1287 protein-export membrane protein  
    
por Rhea 23.02.2017 / 08:23

7 respostas

5

Você pode usar awk :

awk 'NR==FNR{         # On the first file,
       a[$0];         # store the content in the array a
       next
     } 
     {                        # On the second file, 
         for(i in a)          # for all element in the array a,
            if(index($0,i)) { # check if there is match in the current record
               print "C" $0   # in that case print it with the record separator
               next
            }
     }' fileB RS='\nC' fileA
C    02030 *Bacterial chemotaxis* [PATH:aap02030]
D      NT05HA_0919 maltose-binding periplasmic protein
D      NT05HA_0918 maltose-binding periplasmic protein 
C    03070 *Bacterial secretion system* [PATH:aap03070]
D      NT05HA_1309 protein-export membrane protein SecD 
D      NT05HA_1310 protein-export membrane protein SecF 
D      NT05HA_1819 preprotein translocase subunit SecE
D      NT05HA_1287 protein-export membrane protein  
    
por 23.02.2017 / 09:25
4

Se você quiser corresponder exatamente na parte que está entre C <word> e [PATH:...] (e supondo que * em sua amostra seja apenas para ênfase e não faça parte dos dados reais), você poderia fazer :

awk '
  !start {all_strings[$0]; next}
  /^C/ {
    key = $0

    # strip the leading C <word>:
    sub(/^C[[:blank:]]+[^[:blank:]]+[[:blank:]]*/, "", key)

    # strip the trailing [...]:
    sub(/[[:blank:]]*\[[^]]*][[:blank:]]*$/, "", key)
    selected = key in all_strings
  }
  selected' fileB start=1 fileA

Além da confiabilidade adicional (por exemplo, Bacterial secretion corresponderia apenas a um registro Bacterial secretion e não também a Bacterial secretion system ), ela também é muito eficiente, pois os arquivos são lidos apenas uma vez e a correspondência é apenas uma pesquisa de tabela de hash em oposição a várias pesquisas de substring ou correspondências de expressões regulares.

    
por 23.02.2017 / 10:29
3

Tenho certeza que vou ser derrubado por usar um loop, mas ainda assim ... aqui está uma maneira de fazer isso.

#!/bin/bash

while read -r line; do
        sed -n "/$line/,/^C/p" fileA | sed '$d'
        done < fileB

Exemplo:

./bacteria.sh 
C    02030 *Bacterial chemotaxis* [PATH:aap02030]
D      NT05HA_0919 maltose-binding periplasmic protein
D      NT05HA_0918 maltose-binding periplasmic protein 
C    03070 *Bacterial secretion system* [PATH:aap03070]
D      NT05HA_1309 protein-export membrane protein SecD 
D      NT05HA_1310 protein-export membrane protein SecF 
D      NT05HA_1819 preprotein translocase subunit SecE
D      NT05HA_1287 protein-export membrane protein  

Em que fileA e fileB são seus arquivos de exemplo.

Divisão de regex:

sed -n "/$line/,/^C/p" fileA | sed '$d'

Imprima linhas entre $line e a próxima linha que começa com a letra C , mas exclua ( sed '$d' ) a linha final, pois ela é usada apenas como um "marcador de parada".

sed --version
sed (GNU sed) 4.2.2

bash --version
GNU bash, version 4.2.46(1)-release (x86_64-redhat-linux-gnu)
    
por 23.02.2017 / 09:19
3

Com grep e sed em uma linha:

for i in $(grep -f fileb filea | awk '{print $2}'); do sed -e 's/^C/\nC/g' filea | sed -n "/$i/,/^ *$/p" | grep -v "^$"; done

Isto é:

Pegue os padrões para pesquisar com:

grep -f fileb filea | awk '{print $2}'

Insira uma linha em branco antes de cada linha iniciada por C:

sed -e 's/^C/\nC/g' filea

Retire do padrão para a linha em branco:

sed -n "/$i/,/^ *$/p"

Exclua linhas em branco para obter a saída desejada:

grep -v "^$"

Tudo isso dentro de um loop for para poder fazer o mesmo processo para cada padrão no arquivo.

    
por 23.02.2017 / 09:36
3

Os dados em fileA são divididos em registros que começam com C em uma nova linha. Cada registro é dividido em inte campos que começam com D em uma nova linha.

Precisamos ler as linhas de fileB e usá-las para consultar o primeiro campo de cada registro em fileA :

while read -r query; do
    awk -vq="$query" 'BEGIN { RS="^C|\nC"; FS=OFS="\nD" } $1 ~ q {print "C" $0}' fileA
done <fileB

Estou definindo o separador de registro ( RS ) para corresponder a C no início de uma linha ou depois de uma nova linha ou talvez não consigamos corresponder a nada no primeiro registro corretamente. Estou usando uma variável awk , q , para manter o valor lido do arquivo e correspondi o primeiro campo de cada registro a esse valor.

Resultado:

C    02030 *Bacterial chemotaxis* [PATH:aap02030]
D      NT05HA_0919 maltose-binding periplasmic protein
D      NT05HA_0918 maltose-binding periplasmic protein
C    03070 *Bacterial secretion system* [PATH:aap03070]
D      NT05HA_1309 protein-export membrane protein SecD
D      NT05HA_1310 protein-export membrane protein SecF
D      NT05HA_1819 preprotein translocase subunit SecE
D      NT05HA_1287 protein-export membrane protein
    
por 23.02.2017 / 10:32
2

A solução geral que abrange todos os tamanhos dos seus arquivos: link

EXEMPLO: Grepping n linhas para m expressões regulares.

A solução mais simples para o grep de um arquivo grande para muitos regexps é:

grep -f regexps.txt bigfile

Ou se os regexps forem sequências fixas:

grep -F -f regexps.txt bigfile

Existem 3 fatores limitantes: CPU, RAM e E / S de disco.

A RAM é fácil de medir: se o processo grep ocupa a maior parte da sua memória livre (por exemplo, quando a parte superior é executada), a RAM é um fator limitante.

A CPU também é fácil de medir: se o grep levar 90% da CPU no topo, a CPU é um fator limitante e a paralelização acelerará isso.

É mais difícil ver se a E / S de disco é o fator limitante e, dependendo do sistema de disco, pode ser mais rápido ou mais lento paralelizar. A única maneira de saber com certeza é testar e medir.

Fator limitante: RAM

O grep normal -f regexs.txt bigfile funciona não importa o tamanho do bigfile, mas se o regexps.txt é tão grande que não cabe na memória, então você precisa dividir isso.

grep -F leva cerca de 100 bytes de RAM e o grep leva cerca de 500 bytes de RAM por 1 byte de regexp. Então, se regexps.txt é 1% da sua memória RAM, então pode ser muito grande.

Se você puder converter seus regexps em strings fixas, faça isso. Por exemplo. Se as linhas que você está procurando no bigfile, tudo se parece com:

ID1 foo bar baz Identifier1 quux
fubar ID2 foo bar baz Identifier2

então seu regexps.txt pode ser convertido de:

ID1.*Identifier1
ID2.*Identifier2

para:

ID1 foo bar baz Identifier1
ID2 foo bar baz Identifier2

Desta forma, você pode usar o grep -F, que consome cerca de 80% menos memória e é muito mais rápido.

Se ainda não couber na memória, você pode fazer isso:

parallel --pipepart -a regexps.txt --block 1M grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

O 1M deve ser sua memória livre dividida pelo número de núcleos e dividida por 200 para grep -F e por 1000 para grep normal. No GNU / Linux você pode fazer:

free=$(awk '/^((Swap)?Cached|MemFree|Buffers):/ { sum += $2 }
          END { print sum }' /proc/meminfo)
percpu=$((free / 200 / $(parallel --number-of-cores)))k

parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

Se você pode viver com linhas duplicadas e ordem errada, é mais rápido fazer isso:

parallel --pipepart -a regexps.txt --block $percpu --compress grep -F -f - bigfile

Fator limitante: CPU

Se a CPU é o fator limitante, a paralelização deve ser feita no regexps:

cat regexp.txt | parallel --pipe -L1000 --round-robin --compress grep -f - -n bigfile |
sort -un | perl -pe 's/^\d+://'

O comando iniciará um grep por CPU e lerá bigfile uma vez por CPU, mas como isso é feito em paralelo, todas as leituras, exceto a primeira, serão armazenadas em cache na RAM. Dependendo do tamanho de regexp.txt, pode ser mais rápido usar --block 10m ao invés de -L1000.

Alguns sistemas de armazenamento têm melhor desempenho ao ler vários blocos em paralelo. Isso vale para alguns sistemas RAID e para alguns sistemas de arquivos de rede. Para paralelizar a leitura de bigfile:

parallel --pipepart --block 100M -a bigfile -k --compress grep -f regexp.txt

Isso dividirá o bigfile em blocos de 100MB e executará o grep em cada um desses blocos. Para paralelizar a leitura de bigfile e regexp.txt, combine os dois usando --fifo:

parallel --pipepart --block 100M -a bigfile --fifo cat regexp.txt \
\| parallel --pipe -L1000 --round-robin grep -f - {}

Se uma linha corresponder a várias expressões regulares, a linha poderá ser duplicada.

Problema maior

Se o problema for grande demais para ser resolvido, provavelmente você está pronto para o Lucene.

    
por 23.02.2017 / 23:55
1

O bash usa apenas builtins para gerar a saída. A lógica do código é bem elementar & auto-explicativo também.

Bash

while IFS= read -r line; do
   IFS=$'\n'; for pat in $(< fileB); do
      case $line in
         [C]*"$pat"* )
            echo "$line"
            unset flag
            break
            ;;

         [D]* )
            ${flag+":"} echo "$line"
            ${flag+":"} break
            ;;

         * ) flag=;;
      esac
   done
done < fileA

Com o GNU sed nós carregamos os padrões na área de espera e depois nos dados do arquivo B, nós os comparamos para determinar a impressão dos blocos C-D + ou não dependendo se uma correspondência foi encontrada na linha C usando os padrões armazenados em espera . O arquivo B não deve começar com um C ou D.

Sed

sed -e '
   /^D/bD

   /^C/{
      x;G
      /\n\(.*\)\n\(.*\n\)\{0,1\}[^\n]*[^\n]*$/{
         s/\(.*\)\n.*//;x
         :loop
            n
         /^C/!bloop
         s/^/\n/;D
      }
      s/\(.*\)\n.*//;x
      :D
      $!N;D
   }

   H;d
' fileB fileA

Perl

perl -l -0777ne '
   push(@A, split $\), next if @ARGV;
   for my $pat ( map { quotemeta } @A ) {
      while ( /^C [^\n]* $pat [^\n]*$/xmg ) {
         my $C = $&;
         print $C .= $& if /\G(\nD.*$)+/xm;
      }
   }
' fileB fileA
    
por 23.02.2017 / 13:30

Tags