awk ile Anahtar-Değer Çiftlerinden Yapılandırılmış Rapor Üretme
Sistem yöneticiliğinin güzel yanlarından biri, günlük hayatta karşılaştığın kaotik veri yığınlarından düzgün, okunabilir raporlar çıkarmayı öğrenmektir. Bu işin tam ortasında da awk oturuyor. Yıllarca Python yazmadan, Perl öğrenmeden, sadece bu tek araçla tonlarca rapor ürettim. Özellikle anahtar-değer çifti formatındaki yapılandırma dosyalarını ve logları işlemek söz konusu olduğunda awk gerçek anlamda parlıyor.
Anahtar-Değer Formatı Neden Bu Kadar Yaygın?
Pratik hayatta ne kadar çok dosya bu formatta olduğunu fark ettiğinizde şaşırıyorsunuz. /etc/os-release, /proc/meminfo, uygulama log dosyaları, yapılandırma dosyaları, monitoring araçlarının çıktıları… Hepsi şu şablona uyuyor:
KEY=value
KEY: value
KEY = value
Ayırıcı bazen =, bazen :, bazen boşluklu =. Büyük-küçük harf tutarsızlıkları ayrı mesele. Gerçek ortamda bu dosyalar genellikle yüzlerce satırdan oluşuyor ve sen sadece birkaç tanesine ihtiyaç duyuyorsun. İşte tam burada awk devreye giriyor.
Temel Anahtar-Değer Ayrıştırma
En basit senaryo ile başlayalım. Diyelim ki /etc/os-release dosyasından sadece işletim sistemi adını ve sürümünü çekmek istiyoruz:
awk -F'=' '/^(NAME|VERSION)=/ {gsub(/"/, "", $2); print $1 ": " $2}' /etc/os-release
Bu komut şunu yapıyor: = karakterini alan ayırıcı olarak kullanıyor, NAME veya VERSION ile başlayan satırları buluyor, tırnak işaretlerini temizliyor ve okunabilir formatta yazdırıyor. Çıktı şöyle görünecek:
NAME: Ubuntu
VERSION: 22.04.3 LTS (Jammy Jellyfish)
Ama gerçek hayatta bu kadar basit olmaz. Gelin daha karmaşık senaryolara geçelim.
Proc Dosya Sisteminden Bellek Raporu
/proc/meminfo klasik bir anahtar-değer dosyasıdır ve sistem yöneticilerinin sık başvurduğu yerlerden biridir. Ham haliyle 50 küsur satır vardır, ama çoğu zaman ihtiyacınız olan sadece 5-6 değerdir:
awk '
BEGIN {
print "===== BELLEK RAPORU ====="
print ""
}
/^MemTotal:/ { total = $2 }
/^MemFree:/ { free = $2 }
/^MemAvailable:/ { available = $2 }
/^Buffers:/ { buffers = $2 }
/^Cached:/ { cached = $2 }
/^SwapTotal:/ { swap_total = $2 }
/^SwapFree:/ { swap_free = $2 }
END {
used = total - free - buffers - cached
printf "Toplam RAM : %8.2f GBn", total/1024/1024
printf "Kullanilan : %8.2f GBn", used/1024/1024
printf "Kullanilabilir: %8.2f GBn", available/1024/1024
printf "Swap Toplam : %8.2f GBn", swap_total/1024/1024
printf "Swap Kullanim : %8.2f GBn", (swap_total - swap_free)/1024/1024
print ""
printf "RAM Kullanim Orani: %.1f%%n", (used/total)*100
}
' /proc/meminfo
Bu script’te dikkat edilmesi gereken birkaç nokta var. BEGIN bloğu dosyayı okumadan önce, END bloğu ise tüm satırlar işlendikten sonra çalışır. Değerleri değişkenlerde biriktirip END bloğunda hesaplama yapmak, awk‘ın en güçlü kullanım kalıplarından biridir.
Çoklu Dosyadan Veri Toplama
Gerçek dünya senaryolarının büyük çoğunluğunda tek bir dosya değil, birden fazla dosyadan veri çekmeniz gerekir. Örneğin bir monitoring sistemi için tüm sunuculardan gelen disk kullanım raporlarını birleştirdiğinizi düşünün. Her dosya şu formatta:
server=web01
disk=/dev/sda1
total_gb=500
used_gb=234
mount=/
Bu formattaki onlarca dosyadan konsolide rapor üretmek için:
awk '
BEGIN {
FS="="
print "SUNUCU DİSK KULLANIM RAPORU"
print "Tarih: " strftime("%Y-%m-%d %H:%M")
print "----------------------------------------"
}
FNR==1 {
# Her yeni dosyada onceki kaydi temizle
if (server != "") {
pct = (used/total)*100
printf "%-10s %-15s %6.1f GB / %6.1f GB (%5.1f%%)n",
server, mount, used, total, pct
}
server=""; disk=""; total=0; used=0; mount=""
}
/^server/ { server=$2 }
/^disk/ { disk=$2 }
/^total_gb/ { total=$2 }
/^used_gb/ { used=$2 }
/^mount/ { mount=$2 }
END {
if (server != "") {
pct = (used/total)*100
printf "%-10s %-15s %6.1f GB / %6.1f GB (%5.1f%%)n",
server, mount, used, total, pct
}
print "----------------------------------------"
}
' /var/reports/disk_*.txt
FNR==1 ifadesi her yeni dosyanın ilk satırında tetiklenir. Bu sayede dosyalar arasında geçiş yaparken bir önceki kaydı yazdırıp değişkenleri sıfırlayabiliyoruz.
Farklı Ayırıcılarla Başa Çıkma
Bazen aynı dosya içinde bile tutarsız ayırıcılar kullanılmış olabilir. Bu durum özellikle eski legacy uygulamaların konfigürasyon dosyalarında sık görülür. Şöyle bir dosyanız varsa:
database_host = 192.168.1.10
database_port=5432
max_connections: 100
timeout : 30
Hem = hem : ile hem de etraflarındaki boşluklarla baş etmeniz gerekiyor:
awk '
{
# Yorum satirlarini ve bos satirlari atla
if ($0 ~ /^[[:space:]]*#/ || $0 ~ /^[[:space:]]*$/) next
# Anahtar-deger ayir (= veya : ile)
if (match($0, /^([^=:]+)[=:](.*)/, arr)) {
key = arr[1]
val = arr[2]
# Basta ve sondaki boslukları temizle
gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", val)
# Buyuk harfe cevir ve kaydet
config[toupper(key)] = val
}
}
END {
print "Veritabani Yapılandırması:"
print " Host :", config["DATABASE_HOST"]
print " Port :", config["DATABASE_PORT"]
print " Max Conn. :", config["MAX_CONNECTIONS"]
print " Timeout :", config["TIMEOUT"]
}
' uygulama.conf
match() fonksiyonunun üçlü parametre kullanımı GNU awk’a özgüdür ama modern sistemlerde varsayılan olarak gawk yüklü geldiği için sorun yaşamazsınız. Regex grupları ile ayırıcıdan bağımsız bir ayrıştırma yapıyoruz burada.
Dinamik Alan Adlarıyla Rapor Üretmek
Yukarıdaki örneklerde hangi anahtarları çekeceğimizi önceden biliyorduk. Ama bazen dosyanın yapısını bilmeden, tüm anahtarları dinamik olarak işlemeniz gerekir. Bu durum özellikle farklı uygulamaların ürettiği monitoring metriklerini topladığınızda ortaya çıkar:
awk -F'=' '
BEGIN {
print "=== YAPILANDIRMA DENETIM RAPORU ==="
OFS=" | "
}
!/^#/ && NF==2 {
# Anahtari ve degeri kaydet
key = $1
val = $2
gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", val)
if (key != "") {
keys[NR] = key
values[key] = val
count++
}
}
END {
printf "%-30s %-40s %sn", "ANAHTAR", "DEGER", "DURUM"
print "----------------------------------------------------------------------"
for (i=1; i<=NR; i++) {
if (keys[i] != "") {
k = keys[i]
v = values[k]
status = (v == "" || v == "null" || v == "none") ? "EKSIK" : "OK"
printf "%-30s %-40s %sn", k, v, status
}
}
print "----------------------------------------------------------------------"
printf "Toplam %d yapılandırma anahtarı bulundu.n", count
}
' uygulama.conf
Bu yaklaşımın güzel yanı, hangi anahtar-değer çiftleri olduğunu bilmeden çalışmasıdır. Ayrıca boş veya tanımsız değerleri otomatik olarak “EKSIK” olarak işaretler; bu tip bir denetim raporu günlük operasyonlarda oldukça işe yarar.
Log Dosyalarından Yapılandırılmış Özet
Uygulama logları da çoğu zaman anahtar-değer formatında yazılır, özellikle modern yapılandırılmış loglama (structured logging) kullanıldığında. Örneğin bir web uygulaması şöyle loglar üretiyorsa:
timestamp=2024-01-15T10:23:45 level=ERROR service=auth user=admin action=login status=failed duration_ms=234
timestamp=2024-01-15T10:23:46 level=INFO service=api user=john action=request status=success duration_ms=45
timestamp=2024-01-15T10:23:47 level=WARN service=db user=system action=query status=slow duration_ms=1205
Bu loglardan servis bazlı özet rapor çıkarmak için:
awk '
{
# Her alanı anahtar-deger olarak ayır
delete kv
for (i=1; i<=NF; i++) {
n = split($i, pair, "=")
if (n==2) kv[pair[1]] = pair[2]
}
svc = kv["service"]
lvl = kv["level"]
dur = kv["duration_ms"] + 0
if (svc != "") {
service_count[svc]++
service_total_ms[svc] += dur
if (lvl == "ERROR") service_errors[svc]++
if (dur > service_max[svc]) service_max[svc] = dur
}
total_requests++
}
END {
print "nSERVIS PERFORMANS RAPORU"
print "========================="
print ""
for (svc in service_count) {
cnt = service_count[svc]
avg = service_total_ms[svc] / cnt
errs = service_errors[svc] + 0
err_rate = (errs / cnt) * 100
print "Servis: " svc
print " Toplam İstek : " cnt
printf " Ort. Sure : %.1f msn", avg
print " Maks. Sure : " service_max[svc] " ms"
printf " Hata Orani : %.1f%%n", err_rate
print ""
}
print "Toplam islem: " total_requests
}
' uygulama.log
Burada ilginç bir teknik kullandık: her satırın alanlarını ($i) split() ile pair dizisine bölüp oradan kv ilişkisel dizisine dolduruyoruz. delete kv komutu her satırda diziyi sıfırlıyor ki bir önceki satırın değerleri sızmaya neden olmasın.
Birden Fazla Kaynağı Karşılaştırma
Bir başka pratik senaryo: iki ortamın (örneğin production ve staging) yapılandırmasını karşılaştırmak istiyorsunuz. Bu işlemi iki awk çalıştırıp fark almak yerine tek bir komutla yapabiliriz:
awk -F'=' '
FNR==NR {
# Ilk dosyayi oku (production)
if (!/^#/ && NF==2) {
gsub(/[[:space:]]/, "", $1)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2)
prod[$1] = $2
}
next
}
{
# Ikinci dosyayi oku (staging) ve karsilastir
if (!/^#/ && NF==2) {
gsub(/[[:space:]]/, "", $1)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2)
staging[$1] = $2
all_keys[$1] = 1
}
}
END {
# Production anahtarlarini da ekle
for (k in prod) all_keys[k] = 1
print "YAPILANDIRMA KARSILASTIRMA RAPORU"
print "================================="
printf "%-30s %-25s %-25s %sn", "ANAHTAR", "PRODUCTION", "STAGING", "DURUM"
print "-------------------------------------------------------------------------------------"
for (k in all_keys) {
p_val = (k in prod) ? prod[k] : "TANIMLI DEGIL"
s_val = (k in staging) ? staging[k] : "TANIMLI DEGIL"
if (p_val == s_val) {
status = "ESLESME"
} else if (!(k in prod)) {
status = "SADECE STAGING"
} else if (!(k in staging)) {
status = "SADECE PROD"
} else {
status = "FARKLI !"
}
printf "%-30s %-25s %-25s %sn", k, p_val, s_val, status
}
}
' production.conf staging.conf
FNR==NR kalıbı awk dünyasının en klasik numaralarından biridir. NR toplam satır numarasını, FNR ise mevcut dosyadaki satır numarasını tutar. İlk dosyayı okurken ikisi eşit olur, ikinci dosyaya geçince NR artmaya devam eder ama FNR sıfırlanır. Bu sayede hangi dosyayı okuduğumuzu anlayabiliyoruz.
Raporu Dosyaya Yönlendirme ve Renklendirme
Raporların sadece terminale yazdırılması gerekmez. Hem dosyaya yazsın hem de renkli terminal çıktısı versin diye şöyle bir yaklaşım kullanabilirsiniz:
awk '
BEGIN {
RED="33[0;31m"
GREEN="33[0;32m"
YELLOW="33[1;33m"
NC="33[0m"
report_file = "/var/log/config_report_" strftime("%Y%m%d") ".txt"
FS="="
}
!/^#/ && NF==2 {
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $1)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", $2)
key=$1; val=$2
# Bos deger kontrolu
if (val == "") {
terminal_line = RED " [EKSIK]" NC " " key " = (bos)"
file_line = " [EKSIK] " key " = (bos)"
} else if (val ~ /^(true|yes|enabled)$/) {
terminal_line = GREEN " [AKTIF]" NC " " key " = " val
file_line = " [AKTIF] " key " = " val
} else {
terminal_line = " [OK] " key " = " val
file_line = " [OK] " key " = " val
}
print terminal_line
print file_line > report_file
}
' uygulama.conf
Renk kodları doğrudan dosyaya yazılırsa dosya okunaksız hale gelir, bu yüzden terminal çıktısı ve dosya çıktısı için ayrı satırlar oluşturuyoruz.
Pratikte Dikkat Edilmesi Gerekenler
Yıllarca bu tür script’ler yazarken öğrendiklerimden bazıları:
- Boşluk hassasiyeti:
KEY=valueileKEY = valuefarklı işlenir,gsub(/[[:space:]]/)ile temizlemeden önce asla varsaymayın. - Yorum satırları: Konfigürasyon dosyalarında
#ile başlayan satırlar yorum satırıdır.!/^[[:space:]]*#/ile bunları filtreleyin, aksi takdirde yanlış eşleşmeler olur. - Boş satırlar:
NF==0veya$0 ~ /^[[:space:]]*$/kontrolü yapmadan ilerlemeyin. - Büyük-küçük harf: Anahtarları karşılaştırmadan önce
toupper()veyatolower()ile normalize edin. - Çoklu satırlı değerler: Bazı konfigürasyon dosyalarında değerler birden fazla satıra yayılabilir (backslash continuation). Bu durum için
awktek başına yeterli olmayabilir,sedile ön işlem yapmanız gerekebilir. - Encoding sorunları: Özellikle eski sistemlerde Latin-1 encoding ile yazılmış konfigürasyon dosyaları UTF-8 bekleyen
awk‘ı karıştırabilir.LANG=C awk ...ile bu sorunu aşabilirsiniz. - gawk vs mawk vs nawk:
match()fonksiyonunun dizi parametresi,strftime(),deletearray gibi özellikler gawk’a özgüdür. Script’i farklı sistemlerde kullanacaksanız#!/usr/bin/gawk -file açıkça belirtin.
Sonuç
awk ile anahtar-değer işleme, sistem yöneticiliğinin az konuşulan ama çok kullanılan becerilerinden biridir. Burada ele aldığımız teknikler; /proc dosya sisteminden bellek raporları üretmek, uygulama konfigürasyonlarını denetlemek, log dosyalarından performans özetleri çıkarmak ve iki ortamı karşılaştırmak gibi günlük görevlerde doğrudan kullanılabilir.
Bu araçların gücü, kurulum gerektirmemelerinden gelir. Python yok, Ruby yok, özel bir kütüphane yok. Sadece birkaç satır awk ve elimizde yapılandırılmış, okunabilir bir rapor var. Özellikle container ortamlarında veya minimal kurulu sistemlerde bu minimalist yaklaşımın değeri kat kat artar.
Bir sonraki adım olarak, buradaki örnekleri kendi ortamınıza uyarlamanızı ve BEGIN/END blokları ile ilişkisel dizi kombinasyonunu iyice özümsemenizi öneririm. Bu iki yapıyı kavradığınızda awk ile neredeyse her türlü metin tabanlı raporu üretebilirsiniz.
