Najpierw przetworz ostatni wiersz za pomocą awk

11

Mam plik danych, który chcę znormalizować awkna podstawie ostatniego punktu danych. Dlatego najpierw chciałbym uzyskać dostęp do ostatniego punktu danych, aby znormalizować dane, a następnie przetwarzać normalnie.

Poniższa metoda, wykorzystująca tacdwa razy, spełnia swoje zadanie, ale może jest bardziej skomplikowana niż to konieczne.

$ 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

Moje pytanie brzmi: czy można uzyskać powyższy wynik, używając tylko awk?

Myślę, że odpowiedź brzmi „Nie, awk skanuje plik linia po linii”, ale jestem otwarty na sugestie dotyczące alternatyw.

Bernhard
źródło

Odpowiedzi:

5

Możesz to zrobić jako rozwiązanie dwuprzebiegowe w awk:

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

Jeśli twoja wersja awk obsługuje blok ENDFILE (np. GNU awk 4+), możesz to zrobić w następujący sposób:

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

Zauważ, że bardziej efektywne jest, seekaby do końca pliku najpierw zobaczyć odpowiedź camh .

Wyjaśnienie

Pierwszy przykład polega na zapamiętywaniu poprzedniego $2, tzn. Jest oceniany tylko wtedy, gdy lokalny licznik linii ( FNR) jest równy globalnemu licznikowi linii ( NR). nextPolecenie przeskakuje do następnej linii, w tym przypadku zapewnia, że ostatni blok jest oceniany tylko gdy drugi argument jest analizowany.

Drugi przykład ma podobną logikę, ale korzysta z bloku ENDFILE, który jest oceniany po osiągnięciu końca pliku wejściowego.

Thor
źródło
Pierwszy przykład działa dobrze, drugi nie $ awk --version GNU Awk 3.1.8. Czy możesz dodać bardzo małe wyjaśnienie, w jaki sposób obsługiwane są dwa pliki wejściowe i co to nextrobi?
Bernhard,
1
@Bernhard: patrz edycja
Thor
6

Jeśli twoje źródło danych jest plikiem, który można odczytać wiele razy (tzn. Nie jest to strumień), powinieneś najpierw użyć, tail(1)aby uzyskać potrzebne dane z ostatniego wiersza i przekazać je do awk do sekwencyjnego przetwarzania pliku. tailbędzie szukał końca pliku, aby odczytać ostatni wiersz bez konieczności odczytu wszystkich danych przed nim.

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

Będzie to duża wygrana dla dużych plików, w których cały plik nie zmieści się w buforze bufora (co oznacza, że ​​trzeba będzie go odczytać z dysku dwa razy, raz dla każdego przejścia), i pomoże w mniejszym stopniu, nie wymagając skanowania wejście, aby przejść do ostatniego wiersza. Mniejsze pliki mogą nie wykazywać dużej różnicy w podejściu dwuprzebiegowym.

camh
źródło
3

Możesz załadować je do tablicy i odczytać do tyłu:

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

Możesz to zrobić bardziej wydajnie, ale ten rodzaj ilustruje, dlaczego awknie jest do tego odpowiednie narzędzie. Kontynuuj używanie, tactam gdzie jest to możliwe, GNU tac jest zazwyczaj najszybszym z wielu narzędzi do tego zadania.

Chris Down
źródło
Zgadzam się, użycie opcji for-loops w awknie jest rozwiązaniem.
Bernhard,