Verarbeiten Sie die letzte Zeile zuerst mit awk

11

Ich habe eine Datendatei, die ich awkbasierend auf dem letzten Datenpunkt normalisieren möchte . Daher möchte ich zuerst auf den letzten Datenpunkt zugreifen, die Daten normalisieren und dann normal verarbeiten.

Die folgende Methode, die taczweimal verwendet wird, erledigt die Aufgabe, ist jedoch möglicherweise komplizierter als erforderlich.

$ 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

Meine Frage lautet wie folgt: Ist es möglich, das obige Ergebnis nur mit awk zu erzielen?

Ich denke, die Antwort lautet "Nein, awk scannt die Datei Zeile für Zeile", aber ich bin offen für Vorschläge für Alternativen.

Bernhard
quelle

Antworten:

5

Sie können dies als Zwei-Pass-Lösung in awk tun:

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

Wenn Ihre Version von awk den ENDFILE-Block unterstützt (z. B. GNU awk 4+), können Sie dies folgendermaßen tun:

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

Beachten Sie, dass es effizienter ist, seekbis zum Ende der Datei zuerst die Antwort von camh zu sehen .

Erläuterung

Das erste Beispiel erinnert sich an das vorherige $2, dh es wird nur ausgewertet, wenn der lokale Zeilenzähler ( FNR) gleich dem globalen Zeilenzähler ( NR) ist. Der nextBefehl springt zur nächsten Zeile. In diesem Fall wird sichergestellt, dass der letzte Block nur ausgewertet wird, wenn das zweite Argument analysiert wird.

Das zweite Beispiel hat eine ähnliche Logik, nutzt jedoch den ENDFILE-Block, der ausgewertet wird, wenn das Ende einer Eingabedatei erreicht ist.

Thor
quelle
Das erste Beispiel funktioniert gut, das zweite nicht $ awk --version GNU Awk 3.1.8. Können Sie vielleicht eine sehr kleine Erklärung hinzufügen, wie zwei Eingabedateien behandelt werden und was nextfunktioniert?
Bernhard
1
@ Bernhard: siehe bearbeiten
Thor
6

Wenn es sich bei Ihrer Datenquelle um eine Datei handelt, die mehrmals gelesen werden kann (dh es handelt sich nicht um einen Stream), sollten Sie zuerst tail(1)die gewünschten Daten aus der letzten Zeile abrufen und diese zur sequentiellen Verarbeitung der Datei an awk übergeben. tailwird bis zum Ende der Datei versuchen, die letzte Zeile zu lesen, ohne alle Daten davor lesen zu müssen.

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

Dies ist ein großer Gewinn bei großen Dateien, bei denen die gesamte Datei nicht in den Puffercache passt (was bedeutet, dass sie bei jedem Durchgang zweimal von der Festplatte gelesen werden muss), und hilft in geringerem Maße, indem sie nicht gescannt werden muss die Eingabe, um zur letzten Zeile zu gelangen. Kleinere Dateien unterscheiden sich möglicherweise nicht wesentlich von einem Zwei-Pass-Ansatz.

camh
quelle
3

Sie können sie in ein Array laden und rückwärts lesen:

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

Sie könnten es effizienter machen, aber diese Art zeigt, warum dies awknicht das richtige Werkzeug ist. Verwenden Sie tacGNU tac, sofern verfügbar, im Allgemeinen das schnellste einer Vielzahl von Tools für diesen Job.

Chris Down
quelle
Ich bin damit einverstanden, dass die Verwendung von for-loops in awknicht die Lösung ist.
Bernhard