Processe a última linha primeiro usando o awk

11

Eu tenho um arquivo de dados que quero normalizar usando awk , com base no último ponto de dados. Por isso, gostaria de acessar primeiro o último ponto de dados, para normalizar os dados e depois processar normalmente.

O método a seguir, usando tac duas vezes, faz o trabalho, mas talvez seja mais complicado do que o necessário.

$ cat file
0 5
1 2
2 3
3 4
$ tac file | awk 'NR==1{norm=$2} {print $1, $2/norm}' | tac
0 1.25
1 0.5
2 0.75
3 1

Minha pergunta é a seguinte: É possível obter o resultado acima usando apenas o awk?

Acho que a resposta é "Não, o awk verifica o arquivo linha por linha", mas estou aberto a sugestões de alternativas.

    
por Bernhard 19.12.2012 / 08:44

3 respostas

5

Você pode fazer isso como uma solução de duas passagens no awk:

awk 'FNR == NR { n = $2; next } { print $1, $2/n }' infile infile

Se a sua versão do awk suporta o bloco ENDFILE (por exemplo, o GNU awk 4+), você pode fazer assim:

awk 'ENDFILE { n = $2 } FNR != NR { print $1, $2/n }' infile infile

Note que é mais eficiente para seek ao final do arquivo ver primeiro a resposta de camh .

Explicação

O primeiro exemplo funciona lembrando-se do $2 anterior, ou seja, ele é avaliado apenas quando o contador de linha local ( FNR ) é igual ao contador de linha global ( NR ). O comando next pula para a próxima linha, nesse caso, garante que o último bloco seja avaliado apenas quando o segundo argumento for analisado.

O segundo exemplo tem lógica similar, mas aproveita o bloco ENDFILE que é avaliado quando o final de um arquivo de entrada é atingido.

    
por 19.12.2012 / 08:53
6

Se a sua fonte de dados é um arquivo que pode ser lido várias vezes (ou seja, não é um fluxo), você deve primeiro usar tail(1) para obter os dados desejados da última linha e passar isso para awk processamento do arquivo. tail procurará o final do arquivo para ler a última linha sem precisar ler todos os dados antes.

awk -v norm=$(tail -n 1 file | cut -d' ' -f2) '{print $1, $2/norm}' file

Esta será uma grande vitória em arquivos grandes em que o arquivo inteiro não caberá no cache do buffer (o que significa que precisaria ser lido do disco duas vezes, uma vez para cada passagem) e ajudaria em menor extensão por não precisando varrer a entrada para chegar à última linha. Arquivos menores podem não mostrar muita diferença para uma abordagem de duas passagens.

    
por 19.12.2012 / 10:18
3

Você pode carregá-los em uma matriz e lê-los de trás para frente:

awk '{x[i++]=$0} END{for (j=i-1; j>=0;) print x[j--] }'

Você pode fazer isso de forma mais eficiente, mas isso ilustra por que awk não é a ferramenta certa para isso. Continue usando tac onde estiver disponível, o GNU tac geralmente é o mais rápido de uma variedade de ferramentas para este trabalho.

    
por 19.12.2012 / 08:50