Leia a tabela após uma linha específica e conte occrences com awk

5

Eu tenho uma tabela em algum lugar em um arquivo de log grande que se parece com este exemplo:

----------------------------
CARTESIAN COORDINATES (A.U.)
----------------------------
  NO LB      ZA    FRAG    MASS        X           Y           Z
   0 C     6.0000    0    12.011         -8.817666638854597         -4.911814574090662         58.264165798697491
   1 C     6.0000    0    12.011         -7.879568488830738         -4.388761616508626         55.950914108733443
   2 C     6.0000    0    12.011         -7.790669273242299         -4.339145245237274         60.527363919786708
   3 C     6.0000    0    12.011         -7.070247938157430         -3.937287748509576         62.694740665963295
   4 C     6.0000    0    12.011         -7.244178391763230         -4.034368638160922         53.748929835486599
   5 H     1.0000    0     1.008         -6.427462410780078         -3.581016558829315         64.562423911622218
   6 H     1.0000    0     1.008         -6.674286700050606         -3.718319003596096         51.850593400164620

--------------------------------
INTERNAL COORDINATES (ANGSTROEM)
--------------------------------

Eu quero dizer a awk para encontrar o CARTESIAN COORDINATES (A.U.) , em seguida, encontrar NO LB e, em seguida, começar a ler a segunda variável em cada linha até atingir o espaço em branco antes de ----- .

Então, eu vou ler todos os elementos Carbono ( C ) Oxigênio ( O ) Hidrogênio ( H )) C H 's e ... então eu entendi quantos C ' H '.

Eu tenho e posso fazer uma variável como C5H2 neste caso, pode ser algo como C3OH4 , alguma idéia?

awk '
/CARTESIAN COORDINATES (A.U.)/ {fcart=1}
fcart &&
/  NO LB/ {scart=1}


/---------------------------/{exit}
' OFS="\t" "$FILENAME"
    
por Raymond Ghaffarian Shirazi 29.09.2015 / 21:41

4 respostas

6

Use este awk :

awk '/CARTESIAN COORDINATES \(A.U.\)/{a=1;next} a==1&&/NO LB/{b=1;next} $0==""{exit}
a==1&&b==1{c[$2]++} END{for(i in c){printf "%s%s", i,c[i]}}' file
  • /CARTESIAN COORDINATES \(A.U.\)/{a=1;next} : Este bloco pesquisa por CARTESIAN COORDINATES (A.U.) e, em seguida, define a variável a para 1 , next significa saltar para a próxima linha e iniciar o processamento novamente com essa linha.
  • a==1&&/NO LB/{b=1;next} verifica se a é 1 e se a segunda string NO LB é encontrada em algum lugar na linha. Ele define a variável b e, em seguida, carrega a linha next .
  • $0==""{exit} : Então, se a linha estiver vazia, saia do processamento (salta para o bloco END{} ).
  • a==1&&b==1{c[$2]++} : Se ambas as correspondências forem encontradas ( a e b equal 1 ), incremente uma matriz chamada c com índice $2 (campo 2). Isso contará as ocorrências de cada valor no segundo campo.
  • END{...} : Isso será executado quando o processamento do arquivo estiver concluído (a matriz está preenchida).
    • for(i in c) é executado por cada elemento na matriz ...
    • printf "%s%s", i,c[i] : ... e imprima o índice e o valor.

A saída (com o seu arquivo de exemplo):

C5H2
    
por chaos 29.09.2015 / 21:59
4

Ainda outra versão do awk:

awk '/NO.*[[:blank:]]LB/,/INTERNAL COORDINATES/ { 
        if($1~/[0-9]/){count[$2]++;}} 
      END {for(i in count){printf "%s%s",i,count[i]}print ""} ' file 

Isso é uma espécie de mistura entre a resposta de Serg e a do Chaos. Ele só será executado entre as linhas correspondentes a NO.*[[:blank:]]LB e INTERNAL COORDINATES . A matriz count conta apenas com linhas cujo primeiro campo é um número.

Se o seu arquivo é exatamente como você mostra onde sucessivos blocos de dados são separados por uma linha vazia, você pode usar o "modo de parágrafo" do Perl, que trata parágrafos como linhas:

perl -00ne 'next unless /CARTESIAN COORDINATES \(A\.U\.\)/; 
            $count{$_}++ for (/\s+\d+\s+(\w+)\s/g); 
            print "$_$count{$_}" for keys(%count)' file 

Explicação

  • -00 : ativa o modo de parágrafo;
  • next unless /CARTESIAN COORDINATES \(A\.U\.\)/; pule este parágrafo se não corresponder a CARTESIAN COORDINATES (A.U.) ;
  • $count{$_}++ for (/\n\s+\d+\s+(\w+)\s/g) : a expressão regular procura por um ou mais caracteres em branco ( \s+ ), seguidos por um ou mais dígitos ( \d+ ), um ou mais caracteres em branco novamente e depois um ou mais caracteres ( \w+ ) seguido por um caractere de espaço em branco. Isso deve identificar todos os elementos. %count é um hash, um array associativo. Tem chaves e cada chave está associada a um valor. O $count{$_}++ for ... salvará cada uma das correspondências da regex acima como uma chave nesse hash e incrementará seu valor por um a cada vez que for encontrado. O resultado é um hash que armazena os elementos e o número de vezes que cada um foi encontrado.
  • print "$_$count{$_}" for keys(%count) : para cada um dos elementos (as chaves do hash %count ), imprima o elemento e o número de vezes que ele foi encontrado.

Executar no seu arquivo de exemplo, isso retorna:

$ perl -00ne 'next unless /CARTESIAN COORDINATES \(A\.U\.\)/; 
            $count{$_}++ for (/\s+\d+\s+(\w+)\s/g); 
            print "$_$count{$_}" for keys(%count)' file 
C5H2$

No entanto, falta a nova linha final para que você possa adicioná-la com:

$ perl -00ne 'next unless /CARTESIAN COORDINATES \(A\.U\.\)/; 
                $count{$_}++ for (/\s+\d+\s+(\w+)\s/g); 
                print "$_$count{$_}" for keys(%count); print "\n"' file 
C5H2
    
por terdon 30.09.2015 / 01:20
2

Aqui está um código mais simples:

awk '/NO.*[[:blank:]]LB/,/INTERNAL COORDINATES/ { if ( $2 == "C")  counterC++; if ($2 == "H") counterH++  } END {print "C"counterC"H"counterH} ' coordinates.txt

Exemplo de saída:

$ awk '/NO.*[[:blank:]]LB/,/INTERNAL COORDINATES/ { if ( $2 == "C")  counterC++; if ($2 == "H") counterH++  } END {print "C"c>
C5H2
    
por Sergiy Kolodyazhnyy 29.09.2015 / 22:18
2

A resposta do caos funciona muito bem para realizar o que você quer. Aqui está uma alternativa mais simples apenas no caso,

awk 'BEGIN{}
$2 ~ /^C$/ { countC++; } $2 ~ /^H$/ { countH++ }
END { print "C",countC,"H",countH; }' OFS="" file

Dá a saída C5H2 .

    
por H. Freeze 29.09.2015 / 22:16