awk ile Çoklu Dosyadan Veri Birleştirme ve Çapraz Tablo Oluşturma

Çok dosyayla çalışmak, sysadmin hayatının kaçınılmaz bir parçası. Bir gün üç farklı sunucudan gelen log dosyalarını karşılaştırıyorsunuz, başka bir gün farklı servislerden üretilen metrikleri tek bir raporda birleştirmeniz gerekiyor. Bu tür işler için çoğu kişi Python’a ya da Excel’e uzanıyor, ama terminalden çıkmadan awk ile bu işleri ne kadar hızlı halledebileceğinizi bir kez görünce geri dönmek istemiyorsunuz.

Bu yazıda awk‘ın çok dosyayla nasıl çalıştığını, dosyalar arası veri birleştirmeyi ve çapraz tablo mantığını gerçek dünya senaryolarıyla anlatacağım. Temel awk bilginiz olduğunu varsayıyorum; $1, $2, NR, NF gibi kavramlar size yabancı değil diye devam ediyorum.

awk’ın Çok Dosya Mantığını Anlamak

awk birden fazla dosyayı sırayla işler. Bunu sıradan bir şey gibi görmeyin, çünkü bu davranış bilinçli kullanıldığında güçlü bir araça dönüşüyor. Her dosya için BEGINFILE ve ENDFILE bloklarını kullanabilirsiniz (GNU awk ile), ya da klasik yöntemle FILENAME değişkeninden hangi dosyayı işlediğinizi takip edebilirsiniz.

awk '{ print FILENAME, NR, $0 }' sunucu1.log sunucu2.log sunucu3.log

Bu komut her satırın başına hangi dosyadan geldiğini ve satır numarasını yazdırır. Ama dikkat: NR tüm dosyalar boyunca artan global satır numarasıdır. Sadece mevcut dosyadaki satır numarasını istiyorsanız FNR kullanmanız gerekiyor.

# NR vs FNR farkı
awk '{ print "Dosya:", FILENAME, "| Global Satır:", NR, "| Dosya Satırı:", FNR }' 
    dosya1.txt dosya2.txt

Bu fark kritik. Çok dosya işlemlerinde FNR‘yi yanlış kullanmak saçma sonuçlar doğurabilir ve hatayı bulmak zaman alır.

İlk Dosyayı Referans Alarak Join Yapmak

En klasik awk çok dosya tekniklerinden biri, ilk dosyayı hafızaya alıp ikinci dosyayla eşleştirmek. Veritabanındaki JOIN mantığının terminal versiyonu bu.

Senaryomuz şu: Bir kullanici_id.txt dosyamız var ve kullanıcı ID’leriyle isimlerini içeriyor. Bir de login_log.txt var, sadece kullanıcı ID’lerini ve login zamanlarını tutuyor. Bu iki dosyayı birleştireceğiz.

# kullanici_id.txt içeriği:
# 1001 ahmet.yilmaz
# 1002 mehmet.kaya
# 1003 fatma.demir

# login_log.txt içeriği:
# 1001 2024-01-15 08:32
# 1002 2024-01-15 09:14
# 1001 2024-01-15 11:45
# 1003 2024-01-15 13:22

awk '
FNR == NR { kullanici[$1] = $2; next }
{ print kullanici[$1], $2, $3 }
' kullanici_id.txt login_log.txt

FNR == NR ifadesi sadece ilk dosyayı işlerken true döner. Çünkü ilk dosyanın tüm satırlarında dosya-içi satır numarası ile global satır numarası eşittir. next ile o satır için işlemi bitiriyoruz ve ikinci dosyaya geçince FNR sıfırlanırken NR artmaya devam ettiği için artık FNR != NR oluyor. Bu teknik awk kullanıcılarının neredeyse tamamının bilmesi gereken bir temel kalıp.

Çoklu Dosyadan Veri Toplama ve Özetleme

Üç farklı uygulama sunucusundan gelen access log dosyalarınız var ve her birinde hangi HTTP status code’larının kaç kez döndüğünü bilmek istiyorsunuz. Tek komutla üç dosyayı işleyip özet çıkarabilirsiniz.

# app1.log, app2.log, app3.log dosyaları
# Format: IP TARIH METHOD URL STATUS_CODE RESPONSE_TIME

awk '
{
    status[$NF]++
    sunucu_status[FILENAME][$NF]++
}
END {
    print "=== GENEL OZET ==="
    for (s in status) {
        printf "HTTP %s: %d istekn", s, status[s]
    }
    
    print "n=== SUNUCU BAZINDA ==="
    for (f in sunucu_status) {
        print "Sunucu:", f
        for (s in sunucu_status[f]) {
            printf "  HTTP %s: %dn", s, sunucu_status[f][s]
        }
    }
}
' app1.log app2.log app3.log

Burada awk‘ın çok boyutlu dizilerini kullandık. sunucu_status[FILENAME][$NF] şeklinde iki boyutlu bir dizi oluşturduk. Teknik olarak awk bunu sunucu_status[FILENAME SUBSEP $NF] şeklinde tek boyutlu bir anahtara dönüştürür, ama sözdizimi okunabilir kalıyor.

Gerçek Senaryo: Servis Metrikleri Karşılaştırması

Diyelim ki monitoring sisteminden her 10 dakikada bir oluşturulan metrik dosyalarınız var. Her dosya farklı bir zaman dilimine ait ve içinde servis adı ile o servise ait response time değerleri var. Amacınız her servis için minimum, maksimum ve ortalama değerleri hesaplamak.

# metrik_*.txt dosyaları
# Format: servis_adi response_time_ms

awk '
{
    if (toplam[$1] == "") {
        toplam[$1] = 0
        sayac[$1] = 0
        min[$1] = $2
        max[$1] = $2
    }
    
    toplam[$1] += $2
    sayac[$1]++
    
    if ($2 < min[$1]) min[$1] = $2
    if ($2 > max[$1]) max[$1] = $2
}
END {
    printf "%-20s %8s %8s %10s %8sn", "Servis", "Min(ms)", "Max(ms)", "Toplam", "Ort(ms)"
    printf "%-20s %8s %8s %10s %8sn", "--------------------", "-------", "-------", "----------", "-------"
    for (s in toplam) {
        printf "%-20s %8.1f %8.1f %10d %8.1fn", 
            s, min[s], max[s], toplam[s], toplam[s]/sayac[s]
    }
}
' metrik_*.txt

Glob pattern ile tüm metrik dosyalarını shell’e genişletiyoruz, awk sırayla hepsini işliyor. Bu yaklaşım binlerce dosyayla bile makul sürelerde çalışır.

Çapraz Tablo (Crosstab) Mantığı

Çapraz tablo, iki boyutlu veriyi matris formatında göstermek demek. Klasik pivot table mantığı. awk‘ta bunu dizilerle inşa edip END bloğunda yazdırabilirsiniz.

Senaryomuz: Her satırda sunucu, uygulama ve hata_sayisi olan bir log var. Her sunucu için hangi uygulamanın kaç hata verdiğini çapraz görmek istiyoruz.

# hata_raporu.txt
# Format: sunucu uygulama hata_sayisi
# srv01 nginx 23
# srv01 mysql 7
# srv02 nginx 15
# srv02 redis 42
# srv03 mysql 11
# srv03 redis 8

awk '
{
    matris[$1][$2] += $3
    sunucular[$1] = 1
    uygulamalar[$2] = 1
}
END {
    # Başlık satırı
    printf "%-10s", "Sunucu"
    for (u in uygulamalar) {
        printf " %10s", u
    }
    print ""
    
    # Ayraç
    printf "%-10s", "----------"
    for (u in uygulamalar) {
        printf " %10s", "----------"
    }
    print ""
    
    # Veri satırları
    for (s in sunucular) {
        printf "%-10s", s
        for (u in uygulamalar) {
            if (matris[s][u] != "") {
                printf " %10d", matris[s][u]
            } else {
                printf " %10s", "-"
            }
        }
        print ""
    }
}
' hata_raporu.txt

awk‘ın for (key in array) döngüsü sırasız işler, bu yüzden çıktı sütun sırası öngörülemeyen olabilir. Bunu düzeltmek için sıralı anahtar listesi oluşturmanız gerekir. Bunun için ya çıktıyı sort‘a pipe’layabilir ya da önce anahtarları ayrı bir diziye toplayabilirsiniz.

Dosyalar Arası Fark Bulma (Diff Benzeri İşlem)

Bir dosyada olup diğerinde olmayan kayıtları bulmak çok yaygın bir ihtiyaç. awk bunu güzelce halleder.

# eski_ip_listesi.txt ve yeni_ip_listesi.txt
# Yeni listede olup eskide olmayan IP'leri bul

awk '
FNR == NR { eskiler[$1] = 1; next }
!($1 in eskiler) { print "YENİ:", $1 }
' eski_ip_listesi.txt yeni_ip_listesi.txt

# Eskide olup yenide olmayanları bulmak için:
awk '
FNR == NR { yeniler[$1] = 1; next }
!($1 in yeniler) { print "KALDIRILDI:", $1 }
' yeni_ip_listesi.txt eski_ip_listesi.txt

Bu pattern production ortamında çok işe yarıyor. Firewall kural değişikliklerini takip etmek, DNS kayıtlarını karşılaştırmak, kullanıcı listelerindeki farkları bulmak, hepsinde bu yapıyı kullandım.

Gerçek Dünya: Nginx Log Analizi ve Çapraz Rapor

Birden fazla nginx sunucusu çalıştırıyorsunuz ve haftalık bir IP bazlı istek raporu hazırlamak istiyorsunuz. Hangi IP’nin hangi sunucuya kaç istek attığını görmek istiyorsunuz.

#!/bin/bash
# Birden fazla nginx log dosyasını analiz et

awk '
BEGIN { FS = " " }
{
    # Nginx combined log formatı
    # $1 = IP, $7 = URL, $9 = status code
    ip = $1
    status = $9
    sunucu = FILENAME
    
    # Sadece 4xx ve 5xx hataları takip et
    if (status >= 400) {
        hata_matris[ip][sunucu]++
        toplam_hata[ip]++
    }
}
END {
    print "Hata Raporu - Sunucu Bazinda IP Analizi"
    print "========================================="
    
    for (ip in toplam_hata) {
        if (toplam_hata[ip] >= 10) {  # En az 10 hata olan IP'ler
            printf "nIP: %s (Toplam Hata: %d)n", ip, toplam_hata[ip]
            for (s in hata_matris[ip]) {
                printf "  %s: %d hatan", s, hata_matris[ip][s]
            }
        }
    }
}
' /var/log/nginx/srv1_access.log 
  /var/log/nginx/srv2_access.log 
  /var/log/nginx/srv3_access.log

Bu scripti cron’a koyup her sabah mail ile alırsanız bir önceki günün anomalilerini hızlıca görebilirsiniz.

BEGINFILE ve ENDFILE ile Dosya Bazlı İşlemler

GNU awk (gawk) kullanıyorsanız BEGINFILE ve ENDFILE bloklarından yararlanabilirsiniz. Bu bloklar her dosyanın başında ve sonunda çalışır.

gawk '
BEGINFILE {
    print "n--- Dosya işleniyor:", FILENAME, "---"
    satir_sayaci = 0
    hata_sayaci = 0
}
/ERROR/ {
    hata_sayaci++
}
{
    satir_sayaci++
}
ENDFILE {
    printf "Toplam satır: %d | Hata satırı: %d | Oran: %.2f%%n", 
        satir_sayaci, hata_sayaci, (hata_sayaci/satir_sayaci)*100
}
' /var/log/app/*.log

Bu yapı özellikle her dosya için bağımsız istatistik üretmek istediğinizde çok temiz bir kod yazmanızı sağlıyor. Eski yöntemle FNR == 1 kontrolü yapıp değişkenleri sıfırlamak zorunda kalıyordunuz, BEGINFILE bunu daha okunabilir hale getiriyor.

Birleştirilmiş Çıktıyı Sıralamak

awk‘ın kendi sort mekanizması yoktur. Çıktıyı sıralı vermek istiyorsanız iki seçeneğiniz var: sonuçları sort‘a pipe’lamak ya da tüm veriyi awk içinde biriktirip END‘de yazmak ve dışarıda sort kullanmak.

# Sunucu ve uygulama bazında hata sayısını sıralı göster
awk '
{
    anahtar = $1 ":" $2
    sayac[anahtar] += $3
}
END {
    for (k in sayac) {
        print sayac[k], k
    }
}
' hata_raporu.txt | sort -rn | head -20

sort -rn ile sayısal büyükten küçüğe sıralıyoruz ve ilk 20’yi alıyoruz. Bu pipeline yaklaşımı çoğu zaman awk içinde karmaşık sort mantığı yazmaktan daha pratik.

Veri Doğrulama Senaryosu

İki dosyanın referans bütünlüğünü kontrol etmek de sık karşılaşılan bir durum. Bir siparisler.txt dosyanız var, bir de musteriler.txt. Siparişlerdeki tüm müşteri ID’lerinin müşteri tablosunda olup olmadığını kontrol etmek istiyorsunuz.

awk '
FNR == NR {
    musteriler[$1] = $2  # ID -> İsim
    next
}
{
    siparis_id = $1
    musteri_id = $2
    tutar = $3
    
    if (musteri_id in musteriler) {
        gecerli_siparis++
        print "OK:", siparis_id, "->", musteriler[musteri_id], tutar
    } else {
        gecersiz_siparis++
        print "HATA: Bilinmeyen müşteri ID:", musteri_id, "- Sipariş:", siparis_id
    }
}
END {
    print "n=== Özet ==="
    print "Geçerli sipariş:", gecerli_siparis
    print "Geçersiz sipariş:", gecersiz_siparis
}
' musteriler.txt siparisler.txt

Veri migration projelerinde, ETL süreçlerinde ya da iki sistem arasındaki senkronizasyon kontrollerinde bu tür validasyon scriptleri çok hayat kurtarıyor.

Performans Notu

Büyük dosyalarla çalışırken birkaç şeye dikkat etmek gerekiyor. awk satır satır işler ve bellek tüketimi azdır, ama büyük diziler oluşturduğunuzda bellek kullanımı artabilir. Özellikle milyonlarca benzersiz anahtar içeren diziler sorun çıkarabilir.

Pratik bir kural: Eğer birleştireceğiniz referans dosyası birkaç yüz bin satırı geçiyorsa ve hız kritikse, join komutu ya da veritabanı kullanmayı düşünün. awk bu işi yapar ama join bunun için özelleşmiş bir araçtır.

# Büyük dosyalar için önce sort, sonra join daha hızlı olabilir
sort -k1,1 kullanici_id.txt > kullanici_id_sorted.txt
sort -k1,1 login_log.txt > login_log_sorted.txt
join kullanici_id_sorted.txt login_log_sorted.txt

Ama dosyalar birkaç bin satırsa, awk ile devam edin, ekstra komutlara gerek yok.

Sonuç

awk ile çoklu dosya işlemlerinin özü birkaç temel kavrama dayanıyor. FNR == NR ile ilk dosyayı hafızaya almak, çok boyutlu dizilerle çapraz tablo oluşturmak, FILENAME değişkeniyle hangi dosyayı işlediğinizi takip etmek ve BEGINFILE/ENDFILE ile dosya başı ve sonu mantığı kurmak. Bu dört kavramı anlayınca neredeyse her veri birleştirme senaryosunu çözebilirsiniz.

Kişisel gözlemim şu: Sysadmin işlerinde çoğu veri birleştirme ihtiyacı aslında basit. İki dosyayı ortak bir alan üzerinden eşleştirmek, birkaç dosyadaki değerleri toplamak, farklı kaynaklardan gelen verileri karşılaştırmak. Bunların hepsi Python yazmaya gerek kalmadan awk ile birkaç satırda halledilebilir. Üstelik hiçbir bağımlılık yok, her Linux sistemde çalışıyor ve pipe’larla diğer araçlara kolayca bağlanabiliyor.

Bir de şunu ekleyeyim: Bu teknikleri öğrendikten sonra en büyük kazanç hız değil, zihinsel esneklik oluyor. Veriyi gördüğünüzde “bunu nasıl işlerim” sorusuna anında cevap üretebiliyorsunuz.

Bir yanıt yazın

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