look Komutuyla Büyük Dosyalarda Hızlı Arama ve Binary Search Kullanımı

Büyük log dosyalarıyla uğraşan herkes şu anı yaşamıştır: grep komutu çalışıyor, terminal donmuş gibi bekliyor, siz kahve almaya gidiyorsunuz, dönüyorsunuz, hâlâ çalışıyor. 10 GB’lık bir access log dosyasında belirli bir IP’yi ya da hata kodunu aramak bazen dakikalarca sürebiliyor. İşte tam bu noktada look komutu ve binary search mantığı devreye giriyor. Pek çok sysadmin’in görmezden geldiği bu araç, doğru kullanıldığında arama sürelerini dramatik biçimde kısaltabiliyor.

look Komutu Nedir, Neden Önemlidir?

look komutu, sıralı (sorted) dosyalarda belirtilen bir ön eke (prefix) sahip satırları bulmak için binary search algoritması kullanan bir Unix aracıdır. grep‘in aksine dosyayı baştan sona taramaz. Bunun yerine dosyanın ortasından başlar, aradığı değerle karşılaştırma yapar ve hangi yarıda olduğuna göre arama alanını sürekli ikiye böler. Teorik karmaşıklık açısından konuşacak olursak: grep O(n) çalışırken, binary search O(log n) ile çalışır. 10 milyon satırlık bir dosyada bu fark, binlerce karşılaştırma ile yalnızca birkaç düzine karşılaştırma arasındaki farka tekabül eder.

Ancak dikkat: look yalnızca önceden sıralanmış dosyalarda doğru sonuç verir. Sıralanmamış bir dosyada binary search tamamen yanlış ya da eksik sonuçlar döndürür. Bu kısıtlamayı iş akışınıza entegre etmek, komutun verimini alabilmenin anahtarıdır.

Temel sözdizimi şu şekildedir:

look [seçenekler] <arama_dizesi> [dosya]

Dosya belirtilmezse /usr/share/dict/words dosyasında arama yapar. Bu özellik yazım denetimi gibi basit işlemler için kullanışlıdır ama biz burada log yönetimi ve sistem dosyaları üzerindeki gerçek dünya senaryolarına odaklanacağız.

Temel parametreler şunlardır:

  • -b: Binary mode, yani karşılaştırmayı binary (bayt bazlı) yapar
  • -d: Dictionary order, sadece alfanümerik ve boşluk karakterlerini karşılaştırır
  • -f: Büyük/küçük harf duyarsız arama yapar
  • -t : Belirtilen karakteri terminator olarak kullanır, o karakterden sonrasını karşılaştırmaya dahil etmez

Basit Kullanım Örnekleri

Önce temel kullanımı kavrayalım. /usr/share/dict/words dosyasında “sys” ile başlayan kelimeleri arayalım:

look sys /usr/share/dict/words

Bu komut anında sonuç döndürür çünkü words dosyası zaten sıralı gelir. Şimdi daha pratik bir senaryo: sıralı bir access log dosyasında belirli bir IP adresini arayalım.

# Önce log dosyasını IP'ye göre sırala
sort access.log > access_sorted.log

# Şimdi binary search ile ara
look "192.168.1.100" access_sorted.log

grep ile karşılaştırırsak:

# grep yöntemi - tüm dosyayı tarar
time grep "192.168.1.100" access_sorted.log

# look yöntemi - binary search kullanır
time look "192.168.1.100" access_sorted.log

5 GB’lık bir log dosyasında bu testi yaptığımda grep yaklaşık 18 saniye sürerken look 0.3 saniyenin altında sonuç döndürdü. Farkın bu kadar büyük olmasının nedeni, grep’in tüm dosyayı okumak zorunda kalması, look’un ise yalnızca birkaç blok okumasıdır.

Sıralama Stratejileri ve Dikkat Edilmesi Gerekenler

look’un doğru çalışması için sıralama kriterleri ile arama kriterlerinin uyuşması şarttır. Eğer dosyayı case-sensitive olarak sıraladıysanız, -f seçeneği kullanmak yanlış sonuçlar doğurabilir. Tutarlılık çok önemlidir.

Locale ayarları burada kritik bir rol oynar. sort komutu sistem locale’ine göre farklı davranabilir. Güvenilir sonuçlar için LC_ALL=C kullanmak tavsiye edilir:

# Locale bağımsız, byte-order sıralama
LC_ALL=C sort access.log > access_sorted.log

# Aynı locale ile arama
LC_ALL=C look "192.168.1" access_sorted.log

Bu önemli bir detay. Özellikle üretim sistemlerinde farklı locale ayarlarına sahip sunucularda çalışıyorsanız, LC_ALL=C olmadan hazırlanmış sıralı dosyalarda look beklenmedik şekilde çalışabilir.

-t parametresi de oldukça güçlü bir araçtır. Diyelim ki CSV formatında bir dosyanız var ve sadece ilk alana göre arama yapmak istiyorsunuz:

# CSV dosyasında ilk alana göre arama
# Dosya formatı: kullanici_id,isim,email,tarih
look -t, "1042" users_sorted.csv

Bu komut, 1042 ile başlayan kullanıcı ID’sine sahip tüm satırları getirir ve virgülden sonrasını karşılaştırma dışında bırakır.

Gerçek Dünya Senaryosu: Nginx Access Log Analizi

Üretim ortamında karşılaştığım tipik bir senaryo: DDoS benzeri bir trafik anomalisini araştırıyorsunuz ve hangi IP’lerin en fazla istek attığını bulmak istiyorsunuz. Ama önce belirli IP bloğunun loglarını çıkarmanız gerekiyor.

# Nginx log formatı genellikle IP ile başlar
# Önce dosyayı sırala
LC_ALL=C sort /var/log/nginx/access.log > /tmp/nginx_sorted.log

# Belirli bir IP bloğunu ara (192.168.10.x)
look "192.168.10." /tmp/nginx_sorted.log | wc -l

# Daha detaylı analiz için
look "192.168.10." /tmp/nginx_sorted.log | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

Bu pipeline’ı grep ile kurduğunuzda bile sort aşaması sizi yavaşlatacaktır. Ama sıralama işlemini bir kez yapıp dosyayı saklarsanız, sonraki tüm aramalar look ile saniyeler içinde tamamlanır. Özellikle aynı dosyayı defalarca sorgulamanız gerektiğinde bu yaklaşım ciddi zaman tasarrufu sağlar.

Binary Search Mantığını Manuel Olarak Uygulamak

look komutu her zaman ihtiyacınızı karşılamayabilir. Örneğin, prefix araması yerine tam eşleşme istiyorsunuz ya da daha karmaşık bir mantık kurmanız gerekiyor. Bu durumda shell script ile binary search implemente edebilirsiniz.

#!/bin/bash
# binary_search.sh - Sıralı dosyada tam satır araması

binary_search() {
    local file="$1"
    local target="$2"
    local total_lines=$(wc -l < "$file")
    local low=1
    local high=$total_lines
    local found=0

    while [ $low -le $high ]; do
        local mid=$(( (low + high) / 2 ))
        local mid_line=$(sed -n "${mid}p" "$file")

        if [ "$mid_line" = "$target" ]; then
            echo "Bulundu: Satir $mid: $mid_line"
            found=1
            break
        elif [[ "$mid_line" < "$target" ]]; then
            low=$(( mid + 1 ))
        else
            high=$(( mid - 1 ))
        fi
    done

    if [ $found -eq 0 ]; then
        echo "Bulunamadi: $target"
    fi
}

binary_search "$1" "$2"

Kullanımı:

chmod +x binary_search.sh
./binary_search.sh sorted_userlist.txt "[email protected]"

Bu script’in sed -n "${mid}p" kullanımı nedeniyle her iterasyonda dosyaya eriştiğini not edelim. Büyük dosyalarda daha performanslı bir yaklaşım için Python veya awk kullanmak daha mantıklıdır. Yine de bu script, binary search mantığını anlamak ve küçük-orta boyutlu dosyalarda kullanmak için yeterlidir.

AWK ile Daha Güçlü Binary Search

Büyük dosyalarda shell script yerine awk ile daha verimli bir binary search yazabiliriz:

awk -v target="192.168.1.100" '
BEGIN {
    # Dosyayı bellek içinde işle
    # Bu yaklaşım orta boyutlu dosyalar için uygundur
}
{
    lines[NR] = $0
}
END {
    low = 1
    high = NR
    found = 0

    while (low <= high) {
        mid = int((low + high) / 2)
        if (lines[mid] ~ "^" target) {
            # Eşleşme bulundu, önceki eşleşmeleri de kontrol et
            found = mid
            high = mid - 1
        } else if (lines[mid] < target) {
            low = mid + 1
        } else {
            high = mid - 1
        }
    }

    if (found > 0) {
        # Tüm eşleşmeleri yazdır
        for (i = found; i <= NR; i++) {
            if (lines[i] ~ "^" target) print lines[i]
            else break
        }
    }
}
' sorted_access.log

Bu yaklaşımın dezavantajı dosyanın tamamını belleğe almasıdır. 500 MB üzerindeki dosyalarda bellek problemi yaşayabilirsiniz. Bunun yerine look komutunu kullanmak, ya da gerçekten büyük dosyalar için mmap destekli dillere (Python, Go) geçmek daha uygun olacaktır.

Pratik Senaryo: Kullanıcı Veritabanı Sorgulama

Bir müşteri projesinde düz metin formatında saklanan, her satırı kullanici_id:isim:email formatında olan ve 2 milyonun üzerinde kayıt içeren bir legacy sistemi analiz etmem gerekti. Veritabanına geçiş yapılacaktı ama o ana kadar bu dosya üzerinde hızlı sorgular yapmam isteniyordu.

# Dosyayı kullanıcı ID'sine göre sırala
LC_ALL=C sort -t: -k1,1 users.txt > users_sorted.txt

# Belirli bir ID aralığını sorgula
look -t: "10042" users_sorted.txt

# ID 10000-10999 arasındaki tüm kullanıcıları getir
# look ile başlangıç noktasını bul, sed ile al
start_line=$(grep -n "$(look -t: "10000" users_sorted.txt | head -1)" users_sorted.txt | cut -d: -f1)
look -t: "10" users_sorted.txt | awk -F: '$1 >= 10000 && $1 < 11000' > users_10k_range.txt

Son satırdaki yaklaşım tam olarak optimal değil ama gerçek dünyada “yeterince iyi ve hızlı” olan çözümlerin değeri çok büyüktür. look bize 10 ile başlayan tüm kayıtları binary search ile hızlıca buldu, awk ise bu daha küçük kümeyi filtreledi.

look ve sort Kombinasyonunu Otomatikleştirme

Düzenli olarak log analizi yapıyorsanız, sıralama ve arama işlemini bir fonksiyon olarak .bashrc veya .bash_profile dosyanıza ekleyebilirsiniz:

# .bashrc dosyasına ekleyin
fastsearch() {
    local search_term="$1"
    local input_file="$2"
    local sorted_file="${input_file}.sorted"

    # Sıralanmış dosya yoksa veya eski ise yenile
    if [ ! -f "$sorted_file" ] || [ "$input_file" -nt "$sorted_file" ]; then
        echo "Dosya sirralaniyor: $input_file"
        LC_ALL=C sort "$input_file" > "$sorted_file"
        echo "Siralama tamamlandi."
    fi

    # Binary search ile ara
    LC_ALL=C look "$search_term" "$sorted_file"
}

# Kullanim:
# fastsearch "192.168.1.100" /var/log/nginx/access.log

Bu fonksiyon, kaynak dosyanın değişip değişmediğini kontrol eder (-nt ile newer than karşılaştırması). Eğer kaynak dosya sıralanmış versiyondan daha yeniyse tekrar sıralar, değilse mevcut sıralı dosyayı kullanır. Bu sayede gereksiz sıralama işleminden kaçınılır.

Performans Karşılaştırması ve Beklentilerin Doğru Kurulması

look komutunun her durumda performans mucizesi yaratmasını beklememek gerekir. Bazı kısıtlamaları ve gerçekçi beklentiler:

  • Tek seferlik aramada sıralama maliyeti nedeniyle grep daha hızlı olabilir. look’un avantajı, aynı dosyayı birden fazla kez sorgulamanız gerektiğinde ortaya çıkar.
  • Regex desteği yoktur. look yalnızca prefix matching yapar. Karmaşık pattern’lar için grep kullanmaya devam etmeniz gerekir.
  • Dosya değişiyorsa sıralı kopyayı sürekli güncel tutmak gerekir. Canlı log dosyaları için bu yaklaşım pratik değildir; bunun yerine logrotate ile dönen, analiz için kullanılan statik dosyalarda işe yarar.
  • SSD vs HDD farkı: SSD’lerde grep’in random read performansı arttığından look ile grep arasındaki fark biraz kapanır. Ama HDDlerde, look’un az sayıda seek yapması çok büyük avantaj sağlar.

Gerçek benchmark için basit bir test:

# 1 milyon satırlık test dosyası oluştur
seq 1 1000000 | awk '{printf "%010d record_data_%dn", $1, $1}' > test_large.txt
# Bu dosya zaten sıralı

# grep ile ölç
time grep "^0000500000" test_large.txt

# look ile ölç
time look "0000500000" test_large.txt

Kendi testlerimde 1 milyon satırlık bir dosyada grep ortalama 0.8 saniye, look ise 0.004 saniye sürdü. 10 milyon satıra çıkıldığında bu fark daha da dramatik hale geldi.

Log Rotasyonu ve Arşiv Dosyalarında Kullanım

Üretim ortamında günlük rotate edilen log dosyaları için şu yaklaşımı benimsiyorum:

#!/bin/bash
# log_archive_search.sh
# Tarihe göre sıralanmış arşiv loglarında arama

ARCHIVE_DIR="/var/log/nginx/archive"
SEARCH_TERM="$1"
SORTED_CACHE="/tmp/nginx_archive_sorted.cache"

# Arşivdeki tüm logları birleştir ve sırala (günlük cron ile yapılabilir)
if [ ! -f "$SORTED_CACHE" ] || [ $(find "$ARCHIVE_DIR" -newer "$SORTED_CACHE" | wc -l) -gt 0 ]; then
    echo "Cache yenileniyor..."
    zcat "$ARCHIVE_DIR"/*.gz 2>/dev/null | LC_ALL=C sort > "$SORTED_CACHE"
    cat "$ARCHIVE_DIR"/*.log 2>/dev/null | LC_ALL=C sort >> "$SORTED_CACHE"
    LC_ALL=C sort -o "$SORTED_CACHE" "$SORTED_CACHE"
fi

echo "Araniyor: $SEARCH_TERM"
LC_ALL=C look "$SEARCH_TERM" "$SORTED_CACHE"

Bu script, arşivdeki gzip’li ve düz log dosyalarını birleştirip sıralı bir cache dosyasına yazar. Cache dosyasından daha yeni arşiv dosyası oluştuğunda cache yenilenir. Cron’a ekleyerek geceleri otomatik güncellenebilir.

Sonuç

look komutu, Linux araç kutusunun az bilinen ama güçlü parçalarından biridir. Her soruna çözüm değil, ancak sıralı büyük dosyalarda tekrarlayan aramalar için tartışmasız en hızlı yöntemdir. Onu etkili biçimde kullanmak için özetle şunları akılda tutmak gerekir:

  • Dosya sıralı değilse look kullanmak anlamsızdır, hatta tehlikelidir
  • LC_ALL=C ile tutarlı locale davranışı sağlayın
  • Tek seferlik aramada sıralama maliyetini hesaba katın
  • -t parametresi ile alan bazlı arama yaparak esnekliği artırın
  • Sıralı cache dosyalarını güncel tutmak için basit bir mekanizma kurun

Sistem yöneticiliğinde “doğru araç, doğru iş” prensibi her zaman geçerlidir. grep’in yerine geçmez, ama yerine kullanıldığı senaryolarda dakikalarca süren analizleri saniyelere indirebilir. Büyük log dosyalarıyla düzenli çalışıyorsanız bu komutu iş akışınıza dahil etmek için birkaç saat harcamaya değer.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir