Bir dosyanın sonundan başlayarak Grep


38

Yaklaşık 30.000.000 satırlık (Radius Muhasebe) bir dosyam var ve verilen bir desenin son eşleşmesini bulmam gerekiyor.

Komuta:

tac accounting.log | grep $pattern

İhtiyacım olanı veriyor, ancak çok yavaş çünkü işletim sistemi önce tüm dosyayı okumak ve sonra da boruya göndermek zorunda.

Bu yüzden, dosyayı son satırdan birincisine kadar okuyabilen bir şeye ihtiyacım var.

Yanıtlar:


44

tacyalnızca ilk eşleştirmeden sonra durmak için grep -m 1(GNU varsayarsak grep) kullanmanız durumundagrep

tac accounting.log | grep -m 1 foo

Kimden man grep:

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

Sorunuz, hem örnekte tacve grepkullanma böylece dosyanın tamamını işlemek için ihtiyaç tactür anlamsız olduğunu.

Bu yüzden, kullanmadıkça grep -m, hiç kullanma tac, sadece grepson karşılaşmayı elde etmek için çıktılarını ayrıştır :

grep foo accounting.log | tail -n 1 

Başka bir yaklaşım Perl veya başka bir betik dili kullanmak olacaktır. Örneğin (nerede $pattern=foo):

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

veya

awk '/foo/{k=$0}END{print k}' file

1
Tac kullanıyorum çünkü verilen modelin son eşleşmesini bulmam gerekiyor. Öneriniz "grep -m1" kullanılarak çalıştırma süresi 0m0.597s ila 0m0.007s \ o / arasındadır. Herkese teşekkürler!
Hábner Costa

1
@ HábnerCosta çok açığız. Neden kullandığınızı anlıyorum tac, demek istediğim -m, dosyanın hala iki program tarafından tam olarak okunması gerektiğinden kullanmıyorsanız, işe yaramadığıydı . Aksi takdirde, tüm olayları arayabilir ve sadece sonuncusunu benim gibi tutarsınız tail -n 1.
Terdon

6
Neden "tac [...] tüm dosyayı işlemeli" diyorsunuz? Tac'in yaptığı ilk şey dosyanın sonuna bakmak ve sondan bir blok okumaktır. Bunu strace (1) ile kendiniz doğrulayabilirsiniz. İle birleştiğinde grep -m, oldukça verimli olmalıdır.
Camh

1
@camh ile birleştirildiğinde grep -m. OP kullanmıyordu, -mbu yüzden grep ve tac her şeyi işliyordu.
terdon

Lütfen awkhattın anlamını genişletebilir misiniz ?
Sopalajo de Arrierez

12

Sebebi ise

tac file | grep foo | head -n 1

İlk maçta durmuyor tamponlama yüzünden.

Normalde, head -n 1bir satırı okuduktan sonra çıkar. Öyleyse grepbir SIGPIPE almalı ve ikinci satırını yazdığı anda çıkmalıdır.

Ancak olan şu ki, çıktı bir terminale gitmiyor çünkü greponu tamponlar. Yani, yeterince birikene kadar yazmıyor (GNU grep testimde 4096 bayt).

Bunun anlamı, grep8192 bayt veri yazmadan önce çıkamayacağı, yani muhtemelen birkaç satır.

GNU ile grep, --line-bufferedbir terminale gidip gitmediğine bakılmaksızın, bulunup bulunmadıkça satır yazmasını söyleyen kullanarak daha erken çıkmasını sağlayabilirsiniz . Öyleyse grepbulduğu ikinci hatta çıkacaktır.

Ancak grepyine de GNU ile , -m 1@terdon'un gösterdiği gibi kullanabilirsiniz , bu ilk maçta çıktıkça daha iyidir.

Sizin grepGNU'nuz değilse, grepkullanabilirsiniz sedveya awkyerine. Ama tac bir GNU komutuyla olmak, sana bir sistem bulacaksınız şüphe tacnerede grepGNU değildir grep.

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

Bazı sistemler tail -rGNU ile aynı şeyi yapmak zorundadır tac.

Düzenli (aranabilir) dosyalar için tacve tail -rverimli olduklarını ve dosyaları geriye doğru okudukları için verimli olduklarını unutmayın, yalnızca geriye doğru yazdırmadan önce dosyayı tam olarak okuyamazlar ( @ slm'nin sed yaklaşımı veya tacnormal olmayan dosyalarda olduğu gibi) .

Kullanılamadığı tacveya bulunmadığı sistemlerde tail -r, tek seçenek geriye dönük okumayı aşağıdaki gibi programlama dilleri ile elle perluygulamaktır:

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

Veya:

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

Ancak bu, tüm eşleşmeleri bulmak ve yalnızca sonuncuyu yazdırmak anlamına geliyor.


4

İşte sondan ilk desen oluşumunun yerini bulabilecek olası bir çözüm:

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

Bu -sve -rdüğmelerinden faydalanan tacaşağıdaki gibidir:

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

-r, --regex
interpret the separator as a regular expression

Ancak, çizginin başlangıcı ile kalıp arasındaki her şeyi kaybedeceksiniz.
ychaouche

2

Sed kullanarak

@ Terdon'un ince cevabına bazı alternatif yöntemler kullanarak sed:

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

Örnekler

$ 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

Perl kullanımı

Bonus olarak, Perl'de hatırlanması gereken kolay bir not:

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

Örnek

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

1
Bu (özellikle sedbir tane) grep 5 | tail -n1veya ' dan daha yavaş bir kaç büyüklük sırası olabilir sed '/5/h;$!d;g'. Ayrıca potansiyel olarak çok fazla bellek kullanacaktır. Hala GNU'ları kullandığınızdan daha taşınabilir değil grep -m.
Stéphane Chazelas
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.