Grep dari akhir file ke awal

38

Saya memiliki file dengan sekitar 30.000.000 baris (Radius Accounting) dan saya perlu menemukan kecocokan terakhir dari pola yang diberikan.

Perintah:

tac accounting.log | grep $pattern

memberikan apa yang saya butuhkan, tetapi terlalu lambat karena OS harus terlebih dahulu membaca seluruh file dan kemudian mengirim ke pipa.

Jadi, saya butuh sesuatu yang cepat yang dapat membaca file dari baris terakhir ke yang pertama.

Hábner Costa
sumber

Jawaban:

44

tachanya membantu jika Anda juga menggunakan grep -m 1(dengan asumsi GNU grep) grepberhenti setelah pertandingan pertama:

tac accounting.log | grep -m 1 foo

Dari man grep:

   -m NUM, --max-count=NUM
          Stop reading a file after NUM matching lines.  

Dalam contoh di pertanyaan Anda, baik tacdan grepperlu memproses seluruh file sehingga menggunakan tacagak sia-sia.

Jadi, kecuali Anda menggunakan grep -m, jangan gunakan tacsama sekali, hanya mengurai output grepuntuk mendapatkan kecocokan terakhir:

grep foo accounting.log | tail -n 1 

Pendekatan lain adalah dengan menggunakan Perl atau bahasa scripting lainnya. Misalnya (di mana $pattern=foo):

perl -ne '$l=$_ if /foo/; END{print $l}' file

atau

awk '/foo/{k=$0}END{print k}' file
terdon
sumber
1
Saya menggunakan tac karena saya perlu menemukan kecocokan terakhir dari pola yang diberikan. Menggunakan saran Anda "grep -m1" waktu eksekusi berjalan dari 0m0.597s ke 0m0.007s \ o /. Terimakasih semuanya!
Hábner Costa
1
@ HábnerCosta Anda sangat menyambut. Saya mengerti mengapa Anda menggunakan tac, maksud saya adalah itu tidak membantu kecuali Anda juga menggunakan -mkarena file tersebut masih perlu dibaca secara penuh oleh dua program. Kalau tidak, Anda bisa mencari semua kejadian dan hanya menyimpan yang terakhir seperti yang saya lakukan tail -n 1.
terdon
6
Mengapa Anda mengatakan "tac [...] perlu memproses seluruh file"? Hal pertama yang dilakukan tac adalah mencari ke akhir file dan membaca blok dari akhir. Anda dapat memverifikasi ini sendiri dengan strace (1). Ketika dikombinasikan dengan grep -m, itu harus cukup efisien.
camh
1
@camh bila dikombinasikan dengan grep -mitu. OP tidak menggunakan -msehingga grep dan tac memproses semuanya.
terdon
Bisakah Anda memperluas arti awkgaris?
Sopalajo de Arrierez
12

Alasan mengapa

tac file | grep foo | head -n 1

tidak berhenti pada pertandingan pertama adalah karena buffering.

Biasanya, head -n 1keluar setelah membaca satu baris. Jadi grepharus mendapatkan SIGPIPE dan keluar juga segera setelah ia menulis baris kedua.

Tetapi yang terjadi adalah karena outputnya tidak ke terminal, grepbuffer itu. Yaitu, ini tidak menulisnya sampai cukup terakumulasi (4.096 byte dalam pengujian saya dengan GNU grep).

Apa artinya itu adalah bahwa greptidak akan keluar sebelum ia menulis data 8192 byte, jadi mungkin beberapa baris.

Dengan GNU grep, Anda dapat membuatnya keluar lebih cepat dengan menggunakan --line-bufferedyang memerintahkannya untuk menulis baris segera setelah ditemukan terlepas dari apakah pergi ke terminal atau tidak. Maka grepakan keluar pada baris kedua yang ditemukannya.

Tetapi dengan GNU grep, Anda dapat menggunakan -m 1sebagai gantinya @terdon telah menunjukkan, yang lebih baik karena keluar pada pertandingan pertama.

Jika Anda grepbukan GNU grep, maka Anda bisa menggunakan sedatau awksebaliknya. Tetapi tac sebagai perintah GNU, saya ragu Anda akan menemukan sistem dengan tacmana grepbukan GNU grep.

tac file | sed "/$pattern/!d;q"                             # BRE
tac file | P=$pattern awk '$0 ~ ENVIRON["P"] {print; exit}' # ERE

Beberapa sistem harus tail -rmelakukan hal yang sama seperti yang tacdilakukan GNU .

Perhatikan bahwa, untuk file biasa (yang dapat dicari), tacdan tail -refisien karena mereka membaca file ke belakang, mereka tidak hanya membaca file sepenuhnya dalam memori sebelum mencetaknya ke belakang (seperti yang dilakukan pendekatan @ slm atau tacpada file non-reguler) .

Pada sistem di mana tidak ada tacatau tail -rtersedia, satu-satunya pilihan adalah untuk menerapkan membaca mundur dengan tangan dengan bahasa pemrograman suka perlatau gunakan:

grep -e "$pattern" file | tail -n1

Atau:

sed "/$pattern/h;$!d;g" file

Tapi itu berarti menemukan semua kecocokan dan hanya mencetak yang terakhir.

Stéphane Chazelas
sumber
4

Berikut ini adalah solusi yang mungkin yang akan menemukan lokasi kemunculan pola pertama dari yang terakhir:

tac -s "$pattern" -r accounting.log | head -n 1

Ini memanfaatkan -sdan -rsakelar tacyang adalah sebagai berikut:

-s, --separator=STRING
use STRING as the separator instead of newline

-r, --regex
interpret the separator as a regular expression
mkc
sumber
Kecuali Anda akan kehilangan semua yang ada di antara awal garis dan pola.
ychaouche
2

Menggunakan sed

Menampilkan beberapa metode alternatif untuk jawaban baik @ Terdon menggunakan sed:

$ sed '1!G;h;$!d' file | grep -m 1 $pattern
$ sed -n '1!G;h;$p' file | grep -m 1 $pattern

Contohnya

$ seq 10 > file

$ sed '1!G;h;$!d' file | grep -m 1 5
5

$ sed -n '1!G;h;$p' file | grep -m 1 5
5

Menggunakan Perl

Sebagai bonus, ini sedikit notasi yang lebih mudah diingat di Perl:

$ perl -e 'print reverse <>' file | grep -m 1 $pattern

Contoh

$ perl -e 'print reverse <>' file | grep -m 1 5
5
slm
sumber
1
Itu (terutama yang sed) cenderung beberapa kali lipat lebih lambat dari grep 5 | tail -n1atau sed '/5/h;$!d;g'. Ini juga akan berpotensi menggunakan banyak memori. Ini tidak jauh lebih portabel karena Anda masih menggunakan GNU grep -m.
Stéphane Chazelas