Processe a última linha primeiro usando o awk

11

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

O método a seguir, usando tacduas vezes, faz o trabalho, mas talvez seja mais complicado 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.

Bernhard
fonte

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 sua versão do awk suportar o bloco ENDFILE (por exemplo, GNU awk 4+), você poderá fazer o seguinte:

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

Observe que é mais eficiente, até seeko final do arquivo, ver primeiro a resposta de camh .

Explicação

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

O segundo exemplo tem lógica semelhante, mas tira proveito do bloco ENDFILE, que é avaliado quando o final de um arquivo de entrada é atingido.

Thor
fonte
O primeiro exemplo funciona bem, o segundo não $ awk --version GNU Awk 3.1.8. Você pode talvez adicionar uma explicação muito pequena sobre como são tratados dois arquivos de entrada e o que nextfaz?
21912 Bernhard
1
@Bernhard: veja edição
Thor
6

Se 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 que deseja da última linha e passar para awk para o processamento seqüencial do arquivo. tailprocurará o final do arquivo para ler a última linha sem precisar ler todos os dados anteriores.

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

Isso será uma grande conquista para arquivos grandes, nos quais 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 ajudará em menor grau por não precisar verificar a entrada para chegar à última linha. Arquivos menores podem não mostrar muita diferença em uma abordagem de duas passagens.

camh
fonte
3

Você pode carregá-los em uma matriz e lê-la ao contrário:

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

Você poderia fazê-lo com mais eficiência, mas esse tipo de ilustração awkmostra por que não é a ferramenta certa para isso. Continue usando taconde estiver disponível, o GNU tac é geralmente o mais rápido de uma variedade de ferramentas para este trabalho.

Chris Down
fonte
Eu concordo, usar um forloop no awknão é a solução.
Bernhard