grep ile büyük dosyalarda arama yapmak

status
Published
date
Mar 19, 2020
slug
grep-ile-buyuk-dosyalarda-arama-yapmak
Published
tags
summary
Subflix servisini yazarken elimdeki altyazılar içinde nasıl arama yapacağımı düşündüm. Altyazı dosyalarından zaman ve içerik değerlerini ayrıştırarak bir veritabanına aktarmayı düşündüm fakat elimde çok fazla sayıda altyazı dosyası olduğu için bunu yapmaktan vazgeçtim.
type
Post
Subflix servisini yazarken elimdeki altyazılar içinde nasıl arama yapacağımı düşündüm. Altyazı dosyalarından zaman ve içerik değerlerini ayrıştırarak bir veritabanına aktarmayı düşündüm fakat elimde çok fazla sayıda altyazı dosyası olduğu için bunu yapmaktan vazgeçtim. Ayrıca servisin çalıştığı sunucu üzerinde hali hazırda bazı veritabanı işlemlerinin yükü vardı, üstüne bunu yazmak sunucuyu zorlayacağından dolayı farklı bir çözüm yoluna gittim: grep:
Linux'te grep aracını genelde çıktılara pipe ederek kullanıyoruz: Yani cat dosya.txt | grep 'value' veya ls /usr/lib | grep 'value'şeklinde. Fakat grep ile dosyaların içeriklerinde de arama yapabiliyoruz. Düzenli ifadeler de işin içine girince epey kolaylık sağlıyor.

Bir kaç parametre

İlk denediğim komutlardan biri şu şekildeydi.
grep -r "merhaba" ./subs
Burada -r parametresi, verdiğim dizin altındaki tüm altdizinlerdeki dosyalarda arama yapıyor. Eğer bunu vermezsem, yalnızca verdiğim dizin altındaki dosyalarda arama yapacak.
grep -ri "merhaba" ./subs
  • i parametresini de ekleyerek büyük-küçük harfe takılmadan doğrudan kelimeyi aramasını sağlıyoruz. Böylece kullanıcının girdiyi nasıl verdiğinin önemi kalmıyor.
Bu noktada @ladinu/node-grep aracını kullanıyordum, fakat bir noktada buna gerek olmadığına karar vererek NodeJS'in kendi child_process.exec fonksiyonunu kullandım.

Limit oluşturmak

Son olarak yapmam gereken, çıktılar için bir limit oluşturmaktı. Altyazılarda arama yaparken bazı değerler için binlerce eşleşme çıkabiliyordu. grep'in -m parametresiyle (--max-count) limit belirlenebiliyordu fakat -r parametresiyle birlikte kullanılamıyordu. Ben de biraz daha farklı çözmeye çalıştım. find komutu ile birleştirerek.
find komutu dosyalar için değil, dizinler için arama sağlıyor. grep'i bağlayarak kullanmamın sebebi, find ile limit tanımlayabiliyor olmam. Fakat bunu yaparken Linux'teki bir kaç farklı aracı da birbirine bağlamaya çalıştım: expect, timeout, head.
find ./subs expect 2>/dev/null -exec grep -r "merhaba" {} \;
subs dizini altındaki tüm dizinler içinde grep -r "merhaba" komutunu çalıştır ve bunu expect komutuyla yakala. expect komutu, Linux'te kullanıcıdan girdi almayı sağlıyor. Burada grep'in çıktılarını find'a bağlamamızı sağlıyor.
Elbette kod bundan ibaret değil, şuan hâlâ yalnızca arama yapıyoruz. Şimdi limit oluşturacağız. Öyle gelsin: head.
head komutuyla bir dosyanın baş kısmını (head), yani dosyanın ilk satırlarını okuyoruz. Vereceğimiz sayı ile ilk kaç satır okuyabileceğimizi belirliyoruz.
find ./subs expect 2>/dev/null -exec grep -r "merhaba" {} \; -quit | head -10
Burada yaptığımız şu: find çıktı ürettikçe ekrana yeni satırlar basıyor, tâ ki head komutunun limitine takılana kadar. Eğer ki bu limiti geçerse find çalışmaya devam ediyor. Bu istemediğimiz bir durum olduğu için -quit parametresini de ekliyoruz.
Böylece bu komutla büyük yazı dosyalarında efektik bir arama gerçekleştirebiliyoruz.

Peki ya zaman aşımı?

Dosya sayısı arttıkça ve boyutları büyüdükçe, spesifik bir arama yapmak veya hiç bulunmayan bir değeri aratmak, yukarıdaki yöntemde önlenilemeyecektir. Dolayısıyla arama gerçekleştikten sonra cevabın dönmesi (cevap boş bile olsa) tüm dosyalarda arama gerçekleştikten sonra gerçekleşecektir. Yani dosya sayısı ile cevapların büyüklüğünde doğru orantı bulunur.
Dolayısıyla bunu önlemek için yapabileceğimiz en iyi yöntem zaman aşımı koymaktır. Bunu da timeout komutuyla gerçekleştirebiliriz.
timeout 5s find ./subs expect 2\>/dev/null -exec grep -r "merhaba" {} \; -quit | head -10
Eğer ki 5 saniye içinde verilen değer için dosyalarda en az 10 değer bulamıyorsa terminated çıktısı dönecektir. Pratik bir çözüm olarak bu yöntem kullanılabilir.

Daha mantıklı bir çözüm

Evet... Bu yazıyı mesai bitimi sonrası akşama doğru çay keyfi yaparken yazıyorum.‌ Yukarıdaki anlattığım çözüm şuan sağlıklı çalışıyor: subflix.now.sh
Bu yazıyı yazarken her ne kadar gerçekleştirdiğim bu çözümü anlatmaya çalışıyor olsam da asıl amacım ne yaptığımı, nereyi atladığımı anlamaya çalışmaktı ve yazarken daha kolay bir çözüm buldum: tüm altyazı dosyalarını birleştirmek. Evet, komut, pipe, zaman aşımı saçmalıklarıyla uğraşmaktansa, grep'e tek bir dosya vermek ve -r parametresini kullanmayacağımdan -m parametresini kullanabilirim.
Projedeki bu geliştirmeyi yaptıktan sonra bu yazıya devam edeceğim.

Aynı günün akşamından yazıyorum. Böyle basit bir çözümü neden daha önce düşünemedim, gerçekten hayret ediyorum. Sanırım bazı problemleri karışık olarak gördüğümüzde çözümünün de problemin kendisi gibi karışık olacağını düşünüyor ve zor yollardan çözmeye çalışıyoruz. Basit düşünmek gerekiyor.
find ./subs -name "*.vtt" | xargs cat > merged
Bu komutla subs dizini altındaki tüm .vtt uzantılı dosyaları merged adlı bir dosyada topluyorum, artık grep ile recursive olmayan bir arama gerçekleştirebilirim!
grep -i "merhaba" -m 10 ./subs/merged
Spesifik olmayan sorgularda cevap hızı artık çok daha iyi. Fakat nadir bulunan veya hiç bulunmayan kelimeler için yine tüm dosyayı (373MB) gözden geçirdiği için boyut büyüdükçe bu süre de uzuyor. Bu süreyi nasıl azaltabileceğime bakacağım. Çözemesem de şuanki durumu oldukça tatmin edici.
Şimdilik bu kadar.

Değil. Karakter kodlama ile ilgili bir çözüm buldum. Hız ikiye katlandı.
LC_ALL=C grep -i "merhaba" -m 10 ./subs/merged
Bu durumda regex kullanımının sakıncalı olduğu yazıyor forumda, neyse ki regex kullanmıyorum.
Tamam, bitti artık :)
💃🏻

© Samet 2017 - 2024