comm Komutu ile Sıralı Dosyaları Karşılaştırma ve Fark Bulma

Dosya karşılaştırma işlemleri söz konusu olduğunda aklımıza genellikle diff komutu gelir. Oysa Linux dünyasında çok daha spesifik bir iş için tasarlanmış, ama bir o kadar az tanınan bir araç daha var: comm. Bu komut, sıralı iki dosyayı satır satır karşılaştırır ve üç farklı çıktı sütunuyla size tam olarak neyin nerede olduğunu gösterir. Günlük sysadmin hayatında log analizi, paket listesi karşılaştırması, kullanıcı yönetimi gibi pek çok senaryoda comm gerçek bir zaman kurtarıcı olabilir. Gelin bu komutu her açıdan inceleyelim.

comm Komutu Nedir?

comm, adını “common” kelimesinden alan bir GNU coreutils aracıdır. İki sıralı dosyayı karşılaştırır ve üç sütunlu bir çıktı üretir:

  • Sütun 1: Sadece birinci dosyada bulunan satırlar
  • Sütun 2: Sadece ikinci dosyada bulunan satırlar
  • Sütun 3: Her iki dosyada da bulunan satırlar (ortak satırlar)

diff komutundan temel farkı şudur: diff değişiklikleri hunk’lar halinde gösterir ve metin editleme odaklıdır. comm ise daha matematiksel bir yaklaşımla küme işlemleri yapar. İki listenin kesişimini, farkını ve birleşimini bulmak istediğinizde comm çok daha temiz bir araçtır.

Önemli bir uyarı: comm çalışmadan önce her iki dosyanın da sıralı olması gerekir. Sırasız dosyalarla kullandığınızda ya yanlış sonuçlar alırsınız ya da uyarı mesajı görürsünüz. Bu yüzden pratikte genellikle sort komutuyla birlikte kullanılır.

Temel Kullanım

Önce iki örnek dosya oluşturalım:

# Birinci dosya: Pazartesi sunucuda yüklü paketler
cat > liste_pazartesi.txt << EOF
apache2
curl
git
mysql-server
nginx
php8.1
python3
wget
EOF

# İkinci dosya: Salı sunucuda yüklü paketler
cat > liste_sali.txt << EOF
apache2
curl
docker
git
nginx
nodejs
php8.1
redis
EOF

Dosyalar zaten sıralı. Şimdi comm çalıştıralım:

comm liste_pazartesi.txt liste_sali.txt

Çıktı şöyle görünür:

                apache2
                curl
        docker
                git
mysql-server
                nginx
        nodejs
                php8.1
python3
        redis
wget

İlk bakışta biraz kafa karıştırıcı gelebilir. Girintilere dikkat edin:

  • Girintisiz satırlar (mysql-server, python3, wget): Sadece birinci dosyada var
  • Tek sekme girintili satırlar (docker, nodejs, redis): Sadece ikinci dosyada var
  • Çift sekme girintili satırlar (apache2, curl, git…): Her iki dosyada da var

Parametreler ve Seçenekler

comm komutunun parametreleri son derece sade ama güçlüdür:

-1: Birinci sütunu bastırma (sadece birinci dosyada olanları gizle)

-2: İkinci sütunu bastırma (sadece ikinci dosyada olanları gizle)

-3: Üçüncü sütunu bastırma (her iki dosyada olanları gizle)

–nocheck-order: Dosyaların sıralı olup olmadığını kontrol etme

–check-order: Sıra kontrolünü zorla (varsayılan davranış)

–output-delimiter=STR: Sütunlar arasında sekme yerine özel ayraç kullan

-z veya –zero-terminated: Satır sonu olarak newline yerine null karakter kullan (find -print0 ile uyumlu çalışmak için)

Bu parametrelerin kombinasyonları gerçek gücü ortaya çıkarır. En sık kullanılan kombinasyonlara bakalım.

Pratik Kombinasyonlar

Sadece Ortak Satırları Bulmak (Kesişim)

# Her iki dosyada da olan paketler
comm -12 liste_pazartesi.txt liste_sali.txt

Çıktı:

apache2
curl
git
nginx
php8.1

-12 parametresi birinci ve ikinci sütunu gizler, yani sadece ortak olanları gösterir. Bu küme teorisindeki kesişim (intersection) işlemine karşılık gelir.

Sadece Birinci Dosyada Olanlar

# Pazartesi'den Salı'ya kaldırılan paketler
comm -23 liste_pazartesi.txt liste_sali.txt

Çıktı:

mysql-server
python3
wget

Sadece İkinci Dosyada Olanlar

# Salı günü yeni eklenen paketler
comm -13 liste_pazartesi.txt liste_sali.txt

Çıktı:

docker
nodejs
redis

Sıralama Zorunluluğu ve sort ile Kullanım

comm‘un en kritik gereksinimi dosyaların sıralı olmasıdır. Gerçek dünyada dosyalar her zaman sıralı gelmez. Bu durumu process substitution ile çözebilirsiniz:

# Sırasız dosyaları anında sıralayarak karşılaştırma
comm <(sort dosya1.txt) <(sort dosya2.txt)

# Büyük/küçük harf duyarsız karşılaştırma
comm <(sort -f dosya1.txt) <(sort -f dosya2.txt)

# Sadece ortak olanlar, sırasız dosyalardan
comm -12 <(sort paketler_prod.txt) <(sort paketler_staging.txt)

Bu yapı özellikle komut çıktılarını doğrudan karşılaştırırken çok işe yarar:

# İki sunucunun aktif servislerini karşılaştır
comm -23 
  <(ssh sunucu1 "systemctl list-units --type=service --state=active --no-legend | awk '{print $1}' | sort") 
  <(ssh sunucu2 "systemctl list-units --type=service --state=active --no-legend | awk '{print $1}' | sort")

Gerçek Dünya Senaryoları

Senaryo 1: Sunucu Paket Tutarsızlığı Tespiti

Prod ve staging ortamınızın paket listelerini karşılaştırmak istiyorsunuz. Bir ortamda olmayan paket, potansiyel bir sorun kaynağı olabilir.

# Prod sunucuda yüklü paketleri al
ssh prod-server "dpkg --get-selections | grep -v deinstall | awk '{print $1}' | sort" > prod_paketler.txt

# Staging sunucuda yüklü paketleri al
ssh staging-server "dpkg --get-selections | grep -v deinstall | awk '{print $1}' | sort" > staging_paketler.txt

# Prod'da olup staging'de olmayan paketler (kritik!)
echo "=== PROD'DA VAR, STAGING'DE YOK ==="
comm -23 prod_paketler.txt staging_paketler.txt

# Staging'de olup prod'da olmayan paketler
echo "=== STAGING'DE VAR, PROD'DA YOK ==="
comm -13 prod_paketler.txt staging_paketler.txt

# Her ikisinde de olan paketler
echo "=== ORTAK PAKETLER ==="
comm -12 prod_paketler.txt staging_paketler.txt | wc -l
echo "adet ortak paket mevcut"

Senaryo 2: Log Analizi ile Başarısız Login Tespiti

İki farklı zaman dilimindeki başarısız SSH denemelerini karşılaştırarak kalıcı saldırı kaynaklarını tespit edin:

# Dünkü başarısız SSH IP'lerini çıkar
grep "Failed password" /var/log/auth.log.1 | 
  grep -oE '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}' | 
  sort -u > dunkü_saldirganlar.txt

# Bugünkü başarısız SSH IP'lerini çıkar
grep "Failed password" /var/log/auth.log | 
  grep -oE '[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}' | 
  sort -u > bugünkü_saldirganlar.txt

# Her iki günde de deneme yapan kalıcı saldırganlar
echo "=== KALICI SALDIRGANLАР (Her iki günde de aktif) ==="
comm -12 dunkü_saldirganlar.txt bugünkü_saldirganlar.txt

# Sadece bugün görünen yeni saldırganlar
echo "=== YENİ SALDIRGANLАР ==="
comm -13 dunkü_saldirganlar.txt bugünkü_saldirganlar.txt

Senaryo 3: Kullanıcı Yönetimi ve Grup Karşılaştırması

Bir LDAP ya da lokal kullanıcı listesini beklenen kullanıcı listesiyle karşılaştırmak, özellikle compliance açısından önemlidir:

# Sistemdeki aktif kullanıcılar (UID 1000 ve üzeri)
awk -F: '$3 >= 1000 && $3 != 65534 {print $1}' /etc/passwd | sort > mevcut_kullanicilar.txt

# İnsan kaynakları sisteminden gelen onaylı kullanıcı listesi
# (Bu dosyanın dışarıdan geldiğini varsayalım)
sort onaylı_kullanicilar.txt -o onaylı_kullanicilar.txt

# Sistemde olup onaylı listede olmayan kullanıcılar (potansiyel güvenlik riski!)
echo "=== SİSTEMDE VAR AMA ONAYLANMAMIŞ ==="
comm -23 mevcut_kullanicilar.txt onaylı_kullanicilar.txt

# Onaylı listede olup henüz oluşturulmamış kullanıcılar
echo "=== OLUŞTURULMASI GEREKEN KULLANICILAR ==="
comm -13 mevcut_kullanicilar.txt onaylı_kullanicilar.txt

Senaryo 4: Cron Job Tutarlılığı Kontrolü

Birden fazla sunucuda aynı cron jobların çalışması gerekiyorsa tutarlılık kontrolü yapabilirsiniz:

#!/bin/bash
# Tüm web sunucularında cron tutarlılığını kontrol et

SUNUCULAR=("web1" "web2" "web3")
REFERANS_SUNUCU="web1"

# Referans sunucudan cron listesini al
ssh $REFERANS_SUNUCU "crontab -l 2>/dev/null | grep -v '^#' | sort" > /tmp/cron_referans.txt

for sunucu in "${SUNUCULAR[@]}"; do
  if [ "$sunucu" == "$REFERANS_SUNUCU" ]; then
    continue
  fi
  
  ssh $sunucu "crontab -l 2>/dev/null | grep -v '^#' | sort" > /tmp/cron_${sunucu}.txt
  
  FARK=$(comm -3 /tmp/cron_referans.txt /tmp/cron_${sunucu}.txt | wc -l)
  
  if [ "$FARK" -gt 0 ]; then
    echo "UYARI: $sunucu referanstan farklı!"
    echo "Sadece $REFERANS_SUNUCU'da olan:"
    comm -23 /tmp/cron_referans.txt /tmp/cron_${sunucu}.txt
    echo "Sadece $sunucu'da olan:"
    comm -13 /tmp/cron_referans.txt /tmp/cron_${sunucu}.txt
  else
    echo "OK: $sunucu tutarlı"
  fi
done

Özel Ayraç Kullanımı

Varsayılan sütun ayracı sekme karakteridir. Ancak bazen çıktıyı işlerken özel bir ayraç kullanmak işleri kolaylaştırır:

# Pipe karakteriyle ayırma
comm --output-delimiter="|" dosya1.txt dosya2.txt

# Daha okunabilir çıktı için
comm --output-delimiter=" <> " <(sort liste1.txt) <(sort liste2.txt)

Bu özellikle çıktıyı başka araçlarla parse etmek istediğinizde kullanışlıdır.

comm ile Awk ve Sed Kombinasyonu

comm‘un çıktısını daha ileri işlemler için awk ile birleştirebilirsiniz:

# comm çıktısını etiketli formata dönüştür
comm dosya1.txt dosya2.txt | awk '{
  if (/^tt/) {
    gsub(/^tt/, "")
    print "ORTAK: " $0
  } else if (/^t/) {
    gsub(/^t/, "")
    print "SADECE_2: " $0
  } else {
    print "SADECE_1: " $0
  }
}'

Bu tür bir çıktı loglama veya raporlama scriptlerinde çok işe yarar.

Büyük Dosyalarda Performans

comm satır satır çalışır ve sıralı dosyalar üzerinde çalıştığında oldukça verimlidir. Milyonlarca satırlık log dosyalarında bile makul sürelerde sonuç verir. Ancak birkaç ipucu:

  • Önce filtrele, sonra karşılaştır: Büyük log dosyalarını karşılaştırmadan önce grep ile ilgili satırları çekin.
  • sort’un LC_ALL etkisine dikkat edin: Locale ayarları sıralama davranışını değiştirir. Tutarlı sonuçlar için LC_ALL=C sort kullanın.
  • Geçici dosya yerine process substitution tercih edin: Disk I/O’yu azaltır.
# Büyük dosyalar için optimize edilmiş kullanım
LC_ALL=C comm -12 
  <(LC_ALL=C sort büyük_dosya1.txt) 
  <(LC_ALL=C sort büyük_dosya2.txt) | wc -l

Yaygın Hatalar ve Çözümleri

“comm: file 1 is not in sorted order” hatası: Dosyalarınız sıralı değil. Çözüm: <(sort dosya.txt) kullanın.

Boş satırlar sonucu bozuyor: Karşılaştırmadan önce boş satırları temizleyin:

comm <(grep -v '^$' dosya1.txt | sort) <(grep -v '^$' dosya2.txt | sort)

Büyük/küçük harf farkı: sort -f ile case-insensitive sıralama yapın ama dikkatli olun, comm karşılaştırmayı yine de case-sensitive yapar. En temiz çözüm:

comm <(tr '[:upper:]' '[:lower:]' < dosya1.txt | sort) 
     <(tr '[:upper:]' '[:lower:]' < dosya2.txt | sort)

Trailing whitespace sorunları: Satır sonundaki boşluklar farklılık yaratabilir:

comm <(sed 's/[[:space:]]*$//' dosya1.txt | sort) 
     <(sed 's/[[:space:]]*$//' dosya2.txt | sort)

comm ile diff Arasında Seçim Yapmak

İkisi de karşılaştırma araçları ama farklı ihtiyaçlar için:

  • comm kullanın eğer: İki liste arasındaki küme işlemlerini (kesişim, fark) yapmak istiyorsanız, satır sırası önemli değilse, çıktıyı script içinde kolayca işlemek istiyorsanız.
  • diff kullanın eğer: Metin dosyalarındaki değişiklikleri context’ıyla görmek istiyorsanız, hangi satırın değiştiğini konumsal olarak görmek istiyorsanız, patch oluşturmak istiyorsanız.

Bir sysadmin olarak ben şunu söyleyebilirim: Paket listeleri, kullanıcı listeleri, IP listeleri, domain listeleri gibi liste bazlı karşılaştırmalarda her zaman comm‘a öncelik verin. Çok daha temiz ve doğrudan bir çıktı alırsınız.

Bonus: comm ile Hızlı Rapor Scripti

Aşağıdaki script, iki dizindeki dosya listelerini karşılaştıran basit ama kullanışlı bir araçtır:

#!/bin/bash
# dizin_karsilastir.sh - İki dizinin içeriğini karşılaştır

DIR1="$1"
DIR2="$2"

if [ -z "$DIR1" ] || [ -z "$DIR2" ]; then
  echo "Kullanım: $0 <dizin1> <dizin2>"
  exit 1
fi

DOSYA1=$(mktemp)
DOSYA2=$(mktemp)

# Her dizindeki dosyaları listele (sadece dosya adları)
find "$DIR1" -type f -printf '%Pn' | sort > "$DOSYA1"
find "$DIR2" -type f -printf '%Pn' | sort > "$DOSYA2"

echo "============================================"
echo "KARSILASTIRMA: $DIR1 vs $DIR2"
echo "============================================"

SADECE_1=$(comm -23 "$DOSYA1" "$DOSYA2" | wc -l)
SADECE_2=$(comm -13 "$DOSYA1" "$DOSYA2" | wc -l)
ORTAK=$(comm -12 "$DOSYA1" "$DOSYA2" | wc -l)

echo "Sadece $DIR1'de: $SADECE_1 dosya"
echo "Sadece $DIR2'de: $SADECE_2 dosya"
echo "Her ikisinde de: $ORTAK dosya"
echo ""

if [ "$SADECE_1" -gt 0 ]; then
  echo "--- Sadece $DIR1'de olanlar ---"
  comm -23 "$DOSYA1" "$DOSYA2"
  echo ""
fi

if [ "$SADECE_2" -gt 0 ]; then
  echo "--- Sadece $DIR2'de olanlar ---"
  comm -13 "$DOSYA1" "$DOSYA2"
fi

rm -f "$DOSYA1" "$DOSYA2"

Kullanımı:

chmod +x dizin_karsilastir.sh
./dizin_karsilastir.sh /etc/nginx/conf.d /backup/nginx/conf.d

Sonuç

comm komutu, Linux araç kutusunun az bilinen ama son derece değerli bir üyesidir. Sıralı listeler üzerinde küme işlemleri yapmak için özel olarak tasarlanmış bu araç, doğru kullanıldığında diff veya el ile yazılmış scriptlere göre çok daha temiz ve hızlı sonuçlar verir.

Özellikle şu alanlarda comm‘u araç kutunuzun ön cebine koymanızı öneririm: sunucu tutarlılığı kontrolleri, güvenlik denetimleri, log analizi, paket yönetimi ve kullanıcı yönetimi. sort ile olan doğal birlikteliği ve process substitution desteğiyle birleşince, gerçekten güçlü ve okunabilir one-liner’lar yazabilirsiniz.

Bir sonraki sefere iki liste karşılaştırmanız gerektiğinde diff‘e uzanmadan önce bir saniye durun ve comm‘un işinizi daha iyi çözüp çözmeyeceğini düşünün. Çoğu zaman cevap evet olacaktır.

Bir yanıt yazın

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