İki Dosyayı Karşılaştırma: comm Komutu Kullanımı

Dosyaları karşılaştırmak söz konusu olduğunda aklımıza genellikle diff komutu gelir. Oysa comm komutu, özellikle iki sıralı dosya arasındaki farkları ve benzerlikleri bulmak için çok daha temiz ve pratik bir çözüm sunar. Yıllarca sysadmin olarak çalışırken comm komutunu gerçekten işe yarar bulduğum durumlar oldu: sunucu paket listeleri karşılaştırma, kullanıcı listelerini senkronize etme, log dosyalarındaki farklılıkları bulma gibi günlük operasyonlarda bu komut hayat kurtarır.

comm Komutu Nedir?

comm (communicate ya da compare kelimesinden geldiği düşünülür), iki sıralanmış dosyayı satır satır karşılaştıran ve çıktıyı üç sütun halinde sunan bir komut satırı aracıdır. POSIX standardının bir parçasıdır, yani neredeyse tüm Unix/Linux sistemlerinde hazır olarak gelir, ayrıca kurulum gerektirmez.

Çıktının üç sütunu şu anlama gelir:

  • Sütun 1: Yalnızca birinci dosyada olan satırlar
  • Sütun 2: Yalnızca ikinci dosyada olan satırlar
  • Sütun 3: Her iki dosyada da ortak olan satırlar

diff komutunun aksine comm, daha minimal ve betik dostu bir çıktı üretir. Eğer amacınız “iki liste arasındaki farkı bul” veya “ortak elemanları çek” gibi basit sorulara cevap vermekse, comm sizin için biçilmiş kaftandır.

Temel Kullanım ve Sözdizimi

Temel sözdizimi oldukça basittir:

comm [SEÇENEKLER] dosya1 dosya2

Hemen somut bir örneğe bakalım. Elimizde iki dosya olduğunu düşünelim:

# dosya1.txt
cat dosya1.txt
apache2
curl
git
nginx
vim
wget
# dosya2.txt
cat dosya2.txt
apache2
curl
htop
nano
nginx
tmux

Şimdi comm komutuyla bu iki dosyayı karşılaştıralım:

comm dosya1.txt dosya2.txt

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

                apache2
                curl
git
        htop
        nano
                nginx
tmux
vim
wget

Sütun girintilerine dikkat edin. Birinci sütun (yalnızca dosya1’de olanlar): git, vim, wget. İkinci sütun (yalnızca dosya2’de olanlar): htop, nano, tmux. Üçüncü sütun (her ikisinde de olanlar): apache2, curl, nginx.

Seçenekler ve Parametreler

comm komutunun parametreleri, hangi sütunları gizlemek istediğinizi belirler. Bu biraz alışılmadık bir mantık gibi gelebilir ama pratikte çok kullanışlıdır:

  • -1: Birinci sütunu gizler (yalnızca dosya1’de olan satırları göstermez)
  • -2: İkinci sütunu gizler (yalnızca dosya2’de olan satırları göstermez)
  • -3: Üçüncü sütunu gizler (her iki dosyada da ortak olan satırları göstermez)
  • –nocheck-order: Sıralama kontrolünü devre dışı bırakır
  • –output-delimiter=STR: Sütunlar arasındaki ayırıcıyı değiştirir
  • -z veya –zero-terminated: Satır sonu olarak newline yerine null karakter kullanır

Bu parametreleri birleştirerek istediğiniz sütonu izole edebilirsiniz.

Pratik Kullanım Örnekleri

Yalnızca Ortak Satırları Bulmak

İki dosyadaki ortak satırları bulmak için 1. ve 2. sütunları gizlemeniz yeterli:

comm -12 dosya1.txt dosya2.txt

Çıktı:

apache2
curl
nginx

Bu, “iki listede de bulunan paketler neler?” gibi soruların cevabını saniyeler içinde verir. Örneğin iki farklı sunucuda ortak kurulu paketleri bulmak için birebir kullanabileceğiniz bir yöntemdir.

Yalnızca Birinci Dosyaya Özgü Satırları Bulmak

comm -23 dosya1.txt dosya2.txt

Çıktı:

git
vim
wget

Bu komut “dosya1’de olup dosya2’de olmayan satırlar neler?” sorusunu yanıtlar. Pratikte “birinci sunucuda kurulu olup ikinci sunucuda eksik olan paketler” gibi senaryolarda kullanabilirsiniz.

Yalnızca İkinci Dosyaya Özgü Satırları Bulmak

comm -13 dosya1.txt dosya2.txt

Çıktı:

htop
nano
tmux

Kritik Nokta: Dosyaların Sıralı Olması Zorunluluğu

comm komutunun en önemli gereksinimi, her iki dosyanın da önceden sıralanmış olmasıdır. Sırasız dosyalarla çalışırsanız ya yanlış sonuçlar alırsınız ya da komut uyarı verir.

Dosyalarınızı önce sıralamak için sort komutunu kullanabilirsiniz:

sort dosya1.txt -o dosya1.txt
sort dosya2.txt -o dosya2.txt

Daha iyi bir yaklaşım, sort ve comm komutlarını process substitution ile birleştirmektir. Bu sayede orijinal dosyaları değiştirmeden karşılaştırma yapabilirsiniz:

comm -12 <(sort dosya1.txt) <(sort dosya2.txt)

Bu yaklaşım hem güvenli hem de tek satırda kullanışlıdır. Aşağıdaki örneklerin büyük çoğunluğunda bu yöntemi kullanacağım.

Gerçek Dünya Senaryoları

Senaryo 1: İki Sunucu Arasında Paket Farklılıklarını Bulma

Diyelim ki iki web sunucunuzun aynı paket setine sahip olması gerekiyor. Sunucu konfigürasyonlarının sürüklenmesi (configuration drift) sysadminlerin baş belasıdır. İşte bunu comm ile nasıl yönetirsiniz:

# Birinci sunucudaki paketleri listele
ssh sunucu1 "dpkg --get-selections | awk '{print $1}'" | sort > sunucu1_paketler.txt

# İkinci sunucudaki paketleri listele
ssh sunucu2 "dpkg --get-selections | awk '{print $1}'" | sort > sunucu2_paketler.txt

# Yalnızca sunucu1'de olup sunucu2'de olmayan paketler
echo "=== Sunucu1'de olup Sunucu2'de olmayan paketler ==="
comm -23 sunucu1_paketler.txt sunucu2_paketler.txt

# Yalnızca sunucu2'de olup sunucu1'de olmayan paketler
echo "=== Sunucu2'de olup Sunucu1'de olmayan paketler ==="
comm -13 sunucu1_paketler.txt sunucu2_paketler.txt

Bu basit betik sayesinde iki sunucu arasındaki paket farklılıklarını saniyeler içinde görebilirsiniz.

Senaryo 2: Kullanıcı Listelerini Karşılaştırma

Active Directory veya LDAP entegrasyonunda, sistem kullanıcıları ile yetkili kullanıcı listenizi karşılaştırmanız gerekebilir:

# Sistemdeki kullanıcıları çek (UID 1000 ve üzeri, gerçek kullanıcılar)
awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd | sort > sistem_kullanicilari.txt

# Yetkili kullanıcılar listesi (örneğin HR'dan gelen CSV'den)
sort yetkili_kullanicilar.txt -o yetkili_kullanicilar.txt

# Sistemde olup yetkili listede olmayan kullanıcılar (şüpheli hesaplar!)
echo "=== DİKKAT: Yetkisiz kullanıcı hesapları ==="
comm -23 sistem_kullanicilari.txt yetkili_kullanicilar.txt

# Yetkili listede olup sistemde olmayan kullanıcılar (oluşturulması gerekenler)
echo "=== Oluşturulması gereken hesaplar ==="
comm -13 sistem_kullanicilari.txt yetkili_kullanicilar.txt

Bu tür bir kontrol güvenlik denetimlerinde son derece değerlidir.

Senaryo 3: Log Dosyası Analizi

İki farklı tarihten gelen erişim loglarındaki IP adreslerini karşılaştırmak için comm kullanabilirsiniz:

# Dünkü erişim loglarından IP'leri çek
grep "28/Nov/2024" /var/log/nginx/access.log | 
    awk '{print $1}' | sort -u > dunku_ipler.txt

# Bugünkü erişim loglarından IP'leri çek
grep "29/Nov/2024" /var/log/nginx/access.log | 
    awk '{print $1}' | sort -u > bugunku_ipler.txt

# Her iki günde de erişen IP'ler (düzenli ziyaretçiler/botlar)
echo "=== Her iki günde de erişen IP'ler ==="
comm -12 dunku_ipler.txt bugunku_ipler.txt

# Yalnızca bugün görülen yeni IP'ler
echo "=== Bugün ilk kez görülen IP'ler ==="
comm -13 dunku_ipler.txt bugunku_ipler.txt

Senaryo 4: Yedekleme Doğrulama

Bir dizindeki dosyaların yedeğe düzgün alınıp alınmadığını kontrol etmek için:

# Kaynak dizindeki dosyaları listele
find /var/www/html -type f | sed 's|/var/www/html/||' | sort > kaynak_dosyalar.txt

# Yedek dizinindeki dosyaları listele
find /backup/www -type f | sed 's|/backup/www/||' | sort > yedek_dosyalar.txt

# Kaynak'ta olup yedekte olmayan dosyalar (yedeklenmemiş dosyalar!)
echo "=== UYARI: Yedeklenmeyen dosyalar ==="
comm -23 kaynak_dosyalar.txt yedek_dosyalar.txt

# Yedekte olup kaynakta olmayan dosyalar (silinmiş ama yedekte kalan)
echo "=== Kaynakta silinmiş ama yedekte kalan dosyalar ==="
comm -13 kaynak_dosyalar.txt yedek_dosyalar.txt

Büyük/Küçük Harf Duyarlılığı Sorunu

comm komutu varsayılan olarak büyük/küçük harfe duyarlıdır. Eğer büyük/küçük harf farkını görmezden gelmek istiyorsanız, dosyaları sort -f ile sıralayabilirsiniz ancak bu tam anlamıyla çözüm değildir. En temiz yaklaşım verileri normalize etmektir:

# Her iki dosyayı da küçük harfe çevirip karşılaştır
comm -12 <(sort < <(tr '[:upper:]' '[:lower:]' < dosya1.txt)) 
         <(sort < <(tr '[:upper:]' '[:lower:]' < dosya2.txt))

Çıktıyı Özelleştirme

Varsayılan tab girintisi yerine özel bir ayırıcı kullanmak isteyebilirsiniz. --output-delimiter seçeneği bunun için vardır:

comm --output-delimiter='|' dosya1.txt dosya2.txt

Bu özellikle çıktıyı başka araçlarla işleyecekseniz kullanışlıdır.

Bazen sütunları anlamlandırmak için çıktıyı etiketlemek istersiniz. Bunu awk veya paste ile birleştirerek yapabilirsiniz:

# Her satırın hangi kategoride olduğunu göster
comm dosya1.txt dosya2.txt | awk '{
    if (/^tt/) {print "ORTAK:tt" $0}
    else if (/^t/) {print "SADECE_2:t" $0}
    else {print "SADECE_1:t" $0}
}'

comm ile diff Karşılaştırması

Bu iki komutun farklı amaçlar için tasarlandığını anlamak önemlidir:

  • comm: Sıralı listeleri karşılaştırmak için idealdir. Satırların sırası önemli değildir, içerik önemlidir. Hangi elemanların paylaşıldığını veya eksik olduğunu bulmak için kullanılır.
  • diff: Dosyaların satır bazında nasıl farklılaştığını, hangi satırların değiştirildiğini, eklendiğini veya silindiğini gösterir. Patch oluşturmak ve sıra bağımlı karşılaştırmalar için idealdir.

Eğer iki konfigürasyon dosyasındaki değişiklikleri görmek istiyorsanız diff kullanın. Eğer iki sunucunun kurulu paket listelerini karşılaştırıyorsanız comm kullanın.

Betiklerde comm Kullanımı

comm komutu, betiklerde değişken içinde saklayabileceğiniz temiz çıktılar üretir:

#!/bin/bash

# İki dosyayı karşılaştır ve sonuçlara göre işlem yap
DOSYA1="liste1.txt"
DOSYA2="liste2.txt"

# Yalnızca dosya1'de olanlar
SADECE_1=$(comm -23 <(sort "$DOSYA1") <(sort "$DOSYA2"))

# Yalnızca dosya2'de olanlar
SADECE_2=$(comm -13 <(sort "$DOSYA1") <(sort "$DOSYA2"))

# Her ikisinde de olanlar
ORTAK=$(comm -12 <(sort "$DOSYA1") <(sort "$DOSYA2"))

echo "Toplam ortak eleman sayısı: $(echo "$ORTAK" | wc -l)"
echo "Dosya1'e özgü eleman sayısı: $(echo "$SADECE_1" | wc -l)"
echo "Dosya2'ye özgü eleman sayısı: $(echo "$SADECE_2" | wc -l)"

# Eksik elemanları yeni dosya2'ye ekle
if [ -n "$SADECE_1" ]; then
    echo "Dosya2'ye eksik elemanlar ekleniyor..."
    echo "$SADECE_1" >> "$DOSYA2"
    sort -o "$DOSYA2" "$DOSYA2"
    echo "Tamamlandı."
fi

Boş Girdi ile Başa Çıkma

Betiklerde comm kullanırken dosyaların boş olma ihtimalini göz önünde bulundurun:

#!/bin/bash

# Dosya boş mu kontrol et
if [ ! -s dosya1.txt ] || [ ! -s dosya2.txt ]; then
    echo "UYARI: Dosyalardan biri boş, karşılaştırma atlanıyor."
    exit 1
fi

comm -12 <(sort dosya1.txt) <(sort dosya2.txt)

Ayrıca - karakterini kullanarak standart girdiden okuma yapabilirsiniz, bu da pipeline’larda kullanımı kolaylaştırır:

cat yeni_liste.txt | sort | comm -23 - <(sort mevcut_liste.txt)

Performans Notları

comm komutu son derece hafif ve hızlıdır. Ancak çok büyük dosyalarla çalışırken (milyonlarca satır) sort adımı zaman alabilir. Bu durumda şu optimizasyonları düşünebilirsiniz:

  • Eğer dosyalar zaten sıralıysa tekrar sıralamayın, --nocheck-order ile kontrol adımını atlayabilirsiniz ancak dikkatli kullanın, yanlış sonuçlara yol açabilir.
  • Büyük log dosyaları için önce awk veya grep ile ilgili sütunu/satırları filtreleyin, sonra comm‘a verin.
  • Paralel sıralama için sort --parallel seçeneğini kullanabilirsiniz.
# Büyük dosyalar için optimize edilmiş kullanım
comm -12 
    <(sort --parallel=4 buyuk_dosya1.txt) 
    <(sort --parallel=4 buyuk_dosya2.txt)

Sonuç

comm komutu, sysadmin araç kutusunda hak ettiği yeri her zaman bulamayan ama gerçekten güçlü bir araçtır. Liste karşılaştırma söz konusu olduğunda diff‘e otomatik pilot modunda uzanmak yerine, verilerinizin liste formatında olduğu durumlarda comm‘u değerlendirin.

Özellikle şu senaryolarda comm‘a öncelik vermenizi öneririm: sunucu konfigürasyon denetimleri, kullanıcı hesabı yönetimi, yedekleme doğrulama, güvenlik loglarından anormal aktivite tespiti ve herhangi iki liste arasındaki kesişim veya fark hesaplama işlemleri.

Tek hatırlamanız gereken kritik nokta: dosyaların sıralı olması gerektiği. Process substitution ile sort komutunu birleştirme alışkanlığı edinin, gerisi kendiliğinden gelir. comm -12 <(sort dosya1) <(sort dosya2) kalıbını bir kez ezberlediniz mi, bu komutu çok sık kullandığınızı fark edeceksiniz.

Yorum yapın