Memproses baris terakhir terlebih dahulu menggunakan awk


11

Saya memiliki file data yang ingin dinormalisasi menggunakan awk, berdasarkan datapoint terakhir. Untuk itu, saya ingin mengakses titik data terakhir terlebih dahulu, untuk menormalkan data, kemudian memproses secara normal.

Metode berikut, menggunakan tacdua kali, melakukan pekerjaan, tetapi, mungkin lebih rumit daripada yang diperlukan.

$ 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

Pertanyaan saya adalah sebagai berikut: Apakah mungkin mendapatkan hasil di atas dengan menggunakan awk saja?

Saya pikir jawabannya adalah "Tidak, awk memindai file baris demi baris", tetapi saya terbuka untuk saran untuk alternatif.

Jawaban:


5

Anda dapat melakukannya sebagai solusi dua lulus dalam awk:

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

Jika versi awk Anda mendukung blok ENDFILE (mis. GNU awk 4+), Anda dapat melakukannya seperti ini:

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

Perhatikan bahwa lebih efisien ke seekakhir file terlebih dahulu lihat jawaban camh .

Penjelasan

Contoh pertama berfungsi dengan mengingat yang sebelumnya $2, yaitu hanya dievaluasi ketika penghitung garis lokal ( FNR) sama dengan penghitung garis global ( NR). The nextperintah melompat ke baris berikutnya, dalam hal ini memastikan bahwa blok terakhir hanya dievaluasi ketika argumen kedua parsing.

Contoh kedua memiliki logika yang serupa, tetapi mengambil keuntungan dari blok ENDFILE yang dievaluasi ketika akhir file input tercapai.


Contoh pertama tidak berfungsi dengan baik, kedua tidak $ awk --version GNU Awk 3.1.8. Bisakah Anda menambahkan penjelasan yang sangat kecil tentang bagaimana dua file input ditangani dan apa nextfungsinya?
Bernhard

1
@Bernhard: lihat sunting
Thor

6

Jika sumber data Anda adalah file yang dapat dibaca berkali-kali (artinya ini bukan stream), Anda harus terlebih dahulu menggunakan tail(1)untuk mendapatkan data yang Anda inginkan dari baris terakhir dan meneruskannya ke awk untuk pemrosesan berurutan file tersebut. tailakan berusaha ke akhir file untuk membaca baris terakhir tanpa perlu membaca semua data sebelumnya.

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

Ini akan menjadi kemenangan besar pada file besar di mana seluruh file tidak akan muat dalam buffer cache (artinya perlu dibaca dari disk dua kali, sekali untuk setiap pass), dan akan membantu pada tingkat yang lebih kecil dengan tidak perlu memindai input untuk sampai ke baris terakhir. File yang lebih kecil mungkin tidak menunjukkan banyak perbedaan pada pendekatan dua langkah.


3

Anda bisa memuatnya ke dalam array dan membacanya mundur:

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

Anda bisa melakukannya dengan lebih efisien, tetapi jenis ini menggambarkan mengapa awkbukan alat yang tepat untuk ini. Terus menggunakan tacjika tersedia, GNU tac umumnya yang tercepat dari berbagai alat untuk pekerjaan ini.


Saya setuju, menggunakan for-loop awkbukanlah solusi.
Bernhard
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.