AWK question: Imprima N linhas, começando da terceira linha após dado / pattern / pattern

4

Estou produzindo um arquivo enorme que inclui seções como esta:

~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~ Gradients ~~~~~~~~
~~~~~~~~~ x y z ~~~~~~~~~~
~ ~
~ H         1      0.00781      0.00108      0.00038 ~
~ H         2      0.01271     -0.01507      0.02839 ~
~ C         1     -0.05015     -0.01803      0.01588 ~
~ O         1      0.01733      0.03089     -0.04611 ~
~ O         2      0.01230      0.00114      0.00147 ~
~ ~
~~~~~~~~~~~~~~~~~~~~~~~~

Eu preciso extrair esses números (x y z):

0.00781      0.00108      0.00038
0.01271     -0.01507      0.02839
-0.05015     -0.01803      0.01588
0.01733      0.03089     -0.04611 
0.01230      0.00114      0.00147

Eu escrevi o seguinte script:

awk '/z ~/ {for(i=1; i<=6; i++) {getline; print $4, $5, $6}}' filename

Mas isso me dá uma linha em branco devido à linha "~ ~".

Em outras palavras, toda vez que eu encontrar o padrão /z ~/ , eu quero pular outra linha (padrão + 1) e só imprimir o conteúdo de cinco outras linhas (padrão +2 +3 +4 +5 +6) . E é claro que precisa ser uma operação repetida (fazendo isso, centenas de milhares de vezes).

    
por Marian Koniuszko 06.06.2017 / 13:52

6 respostas

6

Solução

awk :

awk '/z ~/{ n=NR+2 }n && n<=NR && NR<(n+5){ print $4,$5,$6 }' file | column -t

A saída:

0.00781   0.00108   0.00038
0.01271   -0.01507  0.02839
-0.05015  -0.01803  0.01588
0.01733   0.03089   -0.04611
0.01230   0.00114   0.00147
  • NR - número de registro atual

  • n=NR+2 - n aqui aponta para o número da linha "inicial" após a linha padrão

por 06.06.2017 / 14:17
4

A solução mais simples seria simplesmente adicionar outro getline e obter 5 linhas em vez de 6:

$ awk '/z ~/ {getline;for(i=1; i<=5; i++) {getline; print $4, $5, $6}}' file
0.00781 0.00108 0.00038
0.01271 -0.01507 0.02839
-0.05015 -0.01803 0.01588
0.01733 0.03089 -0.04611
0.01230 0.00114 0.00147

Pessoalmente, eu teria feito isso de uma maneira um pouco diferente:

$ awk '/z ~/{f=2;} /~ ~/{f--}; (f==1 && NF>5){print $4, $5, $6} ' file
0.00781 0.00108 0.00038
0.01271 -0.01507 0.02839
-0.05015 -0.01803 0.01588
0.01733 0.03089 -0.04611
0.01230 0.00114 0.00147

A ideia aqui é definir um sinalizador (a variável f ) como 2 na linha que corresponde a z ~ e diminuir seu valor em um a cada vez que encontrarmos uma linha correspondente a ~ ~ . Em seguida, imprimimos os campos 4, 5 e 6 somente nas linhas em que f é 1 e que tem pelo menos 5 campos.

Para ambos os exemplos, para obter uma impressão bonita, você pode usar -vOFS="\t" ou melhor ainda printf :

$ awk '/z ~/{f=2;} /~ ~/{f--}; (f==1 && NF>5){printf "%10s%10s%10s\n", $4, $5, $6} ' file
   0.00781   0.00108   0.00038
   0.01271  -0.01507   0.02839
  -0.05015  -0.01803   0.01588
   0.01733   0.03089  -0.04611
   0.01230   0.00114   0.00147
    
por 06.06.2017 / 14:11
3

Trabalhando

  1. Primeiro, isolamos o intervalo como entre /~ ~/ linhas. Qualquer coisa fora é apagada.
  2. Os próprios valores discrepantes são excluídos também.
  3. Agora temos as linhas adequadas para trabalhar: Nestas, colocamos o marcador \n no início do 4º campo e outro no final do 6º.
  4. Finalmente, retiramos qualquer coisa fora desses marcadores e o que resta são os 4º, 5º e 6º campos + seus espaços intermediários (não modificados).
sed -ne '
   /~ ~/,//!d
   //d
   s/[^[:space:]]\{1,\}/&\n/6
   s/[^[:space:]]\{1,\}/\n&/4
   s/.*\n\(.*\)\n.*//p
' yourfile

Resultados

0.00781      0.00108      0.00038
0.01271     -0.01507      0.02839
-0.05015     -0.01803      0.01588
0.01733      0.03089     -0.04611
0.01230      0.00114      0.00147
    
por 06.06.2017 / 14:59
2

Tente dividir sua operação em algumas etapas, cada uma das quais pode ser obtida usando diferentes programas com sua funcionalidade básica.

Por exemplo, primeiro encontre o padrão /z ~/ e imprima as próximas 6 linhas ( grep -A6 "z ~" ), depois imprima as 4ª, 5ª e 6ª colunas. Por fim, filtre apenas aqueles que tenham um dígito neles, para que a linha entre a correspondência de padrão e os dígitos seja descartada.

Para envolvê-lo em um comando:

grep -A6 "z ~" file | awk '{ print $4, $5, $6 }' | grep -E "[[:digit:]]"'
    
por 06.06.2017 / 16:09
1

No que diz respeito a combinar essas linhas, o padrão /^~ [A-Z]/ seria suficiente e, para cada impressão, os campos correspondentes 4,5 e 6.

A versão do Awk seria:

$ awk '/^~ [A-Z]/{printf("%-8s\t%-8s\t%-8s\n",$4,$5,$6)}' input.txt
0.00781     0.00108     0.00038 
0.01271     -0.01507    0.02839 
-0.05015    -0.01803    0.01588 
0.01733     0.03089     -0.04611
0.01230     0.00114     0.00147 

E a tradução perl da mesma coisa:

$ perl -ane 'printf("%-8s\t%-8s\t%-8s\n",$F[3],$F[4],$F[5]) if /^~ [A-Z]/' input.txt                                     
0.00781     0.00108     0.00038 
0.01271     -0.01507    0.02839 
-0.05015    -0.01803    0.01588 
0.01733     0.03089     -0.04611
0.01230     0.00114     0.00147 

Observe que aqui usamos a função printf() com sinalizadores justificados à esquerda %-8s para fazer a formatação adequada.

O caminho alternativo seria tratar os números desejados como pontos flutuantes e usar %f specifier em vez de %-8s , mas isso adiciona zeros extras a alguns dos números.

    
por 07.06.2017 / 02:09
0

Você pode reconsiderar sua lógica. Em vez de contar linhas, parece mais confiável aqui basear completamente a extração nos padrões das próprias linhas. Talvez este seja de uso:

 awk '/^~ [A-Z]/' t.txt |cut -f 4-6 -d " "

Significado: Extraia apenas as linhas que começam com "~" e onde o terceiro caractere é uma letra maiúscula. Em seguida, alimente a saída do awk por meio do corte, extraindo apenas os campos 4 a 6 (comece a contar com 1) e configure o delimitador para um espaço em branco.

    
por 06.06.2017 / 14:10