Procese la última línea primero con awk

11

Tengo un archivo de datos que quiero normalizar usando awk, basado en el último punto de datos. Por lo tanto, me gustaría acceder primero al último punto de datos, para normalizar los datos y luego procesarlos normalmente.

El siguiente método, usando tacdos veces, hace el trabajo, pero es quizás más complicado de lo necesario.

$ 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

Mi pregunta es la siguiente: ¿Es posible obtener el resultado anterior usando solo awk?

Creo que la respuesta es "No, awk escanea el archivo línea por línea", pero estoy abierto a sugerencias de alternativas.

Bernhard
fuente

Respuestas:

5

Puede hacerlo como una solución de dos pasos en awk:

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

Si su versión de awk admite el bloque ENDFILE (por ejemplo, GNU awk 4+), puede hacerlo así:

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

Tenga en cuenta que es más eficiente seekver primero la respuesta de Camh hasta el final del archivo .

Explicación

El primer ejemplo funciona recordando el anterior $2, es decir, solo se evalúa cuando el contador de línea local ( FNR) es igual al contador de línea global ( NR). El nextcomando salta a la siguiente línea, en este caso asegura que el último bloque solo se evalúa cuando se analiza el segundo argumento.

El segundo ejemplo tiene una lógica similar, pero aprovecha el bloque ENDFILE que se evalúa cuando se alcanza el final de un archivo de entrada.

Thor
fuente
El primer ejemplo funciona bien, el segundo no $ awk --version GNU Awk 3.1.8. ¿Puede agregar una explicación muy pequeña sobre cómo se manejan dos archivos de entrada y qué nexthace?
Bernhard
1
@Bernhard: ver edición
Thor
6

Si su fuente de datos es un archivo que se puede leer varias veces (es decir, no es una secuencia), primero debe usar tail(1)para obtener los datos que desea de la última línea y pasarlos a awk para su procesamiento secuencial del archivo. tailbuscará hasta el final del archivo para leer la última línea sin necesidad de leer todos los datos anteriores.

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

Esto será una gran victoria en archivos grandes donde el archivo completo no cabe en la memoria caché del búfer (lo que significa que debería leerse desde el disco dos veces, una vez por cada pasada), y ayudará en menor medida al no tener que escanear la entrada para llegar a la última línea. Los archivos más pequeños pueden no mostrar mucha diferencia con un enfoque de dos pasos.

camh
fuente
3

Puede cargarlos en una matriz y leerlo al revés:

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

Podría hacerlo de manera más eficiente, pero este tipo de ilustración ilustra por qué awkno es la herramienta adecuada para esto. Continúe usando tacdonde esté disponible, GNU tac es generalmente el más rápido de una variedad de herramientas para este trabajo.

Chris Down
fuente
Estoy de acuerdo, usar a for-loops in awkno es la solución.
Bernhard