sed ile Yerinde Düzenleme: Yedekleme Stratejileri ve Güvenli Dosya Güncelleme

Yıllarca üretim ortamında çalışmış biri olarak şunu rahatlıkla söyleyebilirim: sed -i komutu hem en çok sevdiğim hem de en çok korktuğum araçlardan biridir. Doğru kullanıldığında onlarca sunucudaki konfigürasyon dosyasını saniyeler içinde günceller, yanlış kullanıldığında ise kritik bir config’i yerle bir edebilir. Bu yazıda sed‘in yerinde düzenleme özelliğini, buna eşlik eden yedekleme stratejilerini ve gerçek üretim senaryolarında nasıl güvenli kullanılacağını aktaracağım.

sed -i Nedir ve Neden Bu Kadar Güçlüdür?

sed (stream editor) normalde standart çıktıya yazar, dosyayı değiştirmez. -i parametresi ise “in-place” yani dosyanın kendisini değiştirme modunu aktive eder. Bu basit bir fark gibi görünse de pratikte çok büyük bir etki yaratır.

Klasik sed kullanımı:

sed 's/eski/yeni/g' /etc/uygulama/config.conf

Bu komut sonucu terminale basar, dosyaya dokunmaz. Yerinde düzenleme için:

sed -i 's/eski/yeni/g' /etc/uygulama/config.conf

Peki bu arka planda nasıl çalışır? sed -i aslında şunları yapar: geçici bir dosya oluşturur, tüm içeriği değiştirilmiş haliyle oraya yazar, sonra orijinal dosyanın üzerine taşır. Bu mekanizma önemli çünkü symlink’lerle çalışırken bazen beklenmedik davranışlara yol açabilir. Bunu aklınızın bir köşesinde tutun.

GNU sed (Linux’ta standart olan) ve BSD sed (macOS) arasında -i parametresi kullanımında kritik bir fark vardır. Bu fark özellikle hem Linux hem macOS’ta çalışan scriptler yazarken başınızı ağrıtabilir.

GNU sed (Linux):

sed -i 's/foo/bar/g' dosya.txt
sed -i.bak 's/foo/bar/g' dosya.txt  # yedek ile

BSD sed (macOS):

sed -i '' 's/foo/bar/g' dosya.txt   # boş string zorunlu
sed -i.bak 's/foo/bar/g' dosya.txt  # yedek ile

Taşınabilir script yazıyorsanız bu farkı mutlaka gözetmeniz gerekir.

Yerinde Yedekleme: -i Parametresinin Yedek Uzantısı

sed -i‘nin en değerli özelliği, yerinde düzenleme yaparken otomatik yedek oluşturmasıdır. Parametre hemen ardından uzantı alır:

sed -i.bak 's/DEBUG=false/DEBUG=true/g' /etc/myapp/settings.conf

Bu komut çalıştıktan sonra iki dosyanız olur:

  • /etc/myapp/settings.conf (güncellenmiş)
  • /etc/myapp/settings.conf.bak (orijinal)

Sadece .bak değil, istediğiniz her uzantıyı kullanabilirsiniz:

# Tarih damgalı yedek
sed -i".$(date +%Y%m%d)" 's/old_value/new_value/g' config.cfg

# Versiyon numaralı yedek
sed -i.v1 's/version=1/version=2/g' app.conf

Tarih damgalı yaklaşım özellikle işime yarıyor. Aynı dosyayı birden fazla kez düzenlemeniz gerektiğinde hangi yedeğin ne zaman alındığını net görebiliyorsunuz.

Gerçek Dünya Senaryoları

Senaryo 1: Birden Fazla Sunucuda Nginx Konfigürasyonu Güncelleme

Diyelim ki 20 web sunucunuzda nginx konfigürasyonundaki eski upstream adresini yenisiyle değiştirmeniz gerekiyor. Manuel yaparsanız hem zaman kaybı hem de hata riski yüksek.

#!/bin/bash

SUNUCULAR="web01 web02 web03 web04 web05"
ESKI_IP="192.168.1.100"
YENI_IP="192.168.1.200"
CONF_DOSYA="/etc/nginx/conf.d/upstream.conf"

for sunucu in $SUNUCULAR; do
    echo "[$sunucu] Guncelleniyor..."
    ssh "$sunucu" "
        # Once yedek al
        cp ${CONF_DOSYA} ${CONF_DOSYA}.$(date +%Y%m%d_%H%M%S)

        # Degisikligi uygula
        sed -i 's/${ESKI_IP}/${YENI_IP}/g' ${CONF_DOSYA}

        # Nginx syntax kontrolu
        nginx -t 2>&1

        # Kontrol basarili ise reload et
        if nginx -t 2>/dev/null; then
            systemctl reload nginx
            echo 'Nginx reload basarili'
        else
            echo 'HATA: Nginx syntax hatasi, degisiklik geri aliniyor'
            YEDEK=$(ls -t ${CONF_DOSYA}.* | head -1)
            cp $YEDEK ${CONF_DOSYA}
        fi
    "
done

Bu scriptte dikkat edin: önce manuel yedek alıyoruz, sonra sed -i ile değişiklik yapıyoruz, ardından syntax kontrolü yapıp sorun varsa geri dönüyoruz. Bu akış üretim ortamı için minimum güvenlik standardıdır.

Senaryo 2: Uygulama Konfigürasyonlarında Toplu Şifre/Anahtar Rotasyonu

Bir güvenlik vakası sonrasında veritabanı şifrenizi birden fazla konfigürasyon dosyasında değiştirmeniz gerekiyor. Bu senaryo biraz daha hassas çünkü şifreler genellikle özel karakterler içerir.

#!/bin/bash

ESKI_SIFRE='Eski$ifre123!'
YENI_SIFRE='Yeni$ifre456!'

# Ozel karakterleri sed icin escape etme
escape_sed() {
    echo "$1" | sed 's/[[.*^$()+?{|]/\&/g'
}

ESKI_ESCAPED=$(escape_sed "$ESKI_SIFRE")
YENI_ESCAPED=$(escape_sed "$YENI_SIFRE")

# Degistirilecek dosyalari bul
find /etc/uygulama -name "*.conf" -o -name "*.properties" | while read dosya; do
    if grep -q "$ESKI_SIFRE" "$dosya"; then
        echo "Guncelleniyor: $dosya"
        sed -i.sekurite_yedek "s/${ESKI_ESCAPED}/${YENI_ESCAPED}/g" "$dosya"
    fi
done

grep -q ile önce dosyanın değiştirilecek içeriği barındırıp barındırmadığını kontrol ediyoruz. Gereksiz yedek oluşturmuyor, gereksiz değişiklik yapmıyoruz. Küçük ama önemli bir pratik.

Senaryo 3: Apache Virtual Host Konfigürasyonu Güncelleme

HTTP’den HTTPS’e geçiş sırasında onlarca virtual host dosyasında ServerName direktiflerini güncellemek zorunda kalmıştım. Şöyle bir yaklaşım kullandım:

# Once ne degistirileceğini gormek icin dry run (degisiklik yapma)
grep -r "ServerName" /etc/apache2/sites-available/ | grep "http://"

# Simdi gercek degisiklik
find /etc/apache2/sites-available/ -name "*.conf" -exec 
    sed -i.pre_https_migration 
    's|ServerName http://|ServerName https://|g' {} ;

# Hangi dosyalar degisti kontrol et
find /etc/apache2/sites-available/ -name "*.pre_https_migration" | while read yedek; do
    orijinal="${yedek%.pre_https_migration}"
    echo "=== $orijinal ==="
    diff "$yedek" "$orijinal"
done

Burada iki önemli nokta var. Birincisi find -exec ile sed kombinasyonu, büyük dizin yapılarını dolaşmak için güçlü bir araç. İkincisi, değişiklik sonrası diff ile ne değiştiğini doğrulama adımı. Bu adımı atlamak iş kazalarının başlangıcıdır.

Çoklu Değişiklik: Tek Geçişte Birden Fazla Desen

Bir dosyada birden fazla şey değiştirmeniz gerektiğinde her değişiklik için ayrı sed -i çalıştırmak hem verimsiz hem de her adımda yedek kirliliği yaratır. Doğru yaklaşım tek geçişte halletmek:

# Yanlis: Her degisiklik icin ayri calistirma
sed -i 's/host=localhost/host=db.internal/g' app.conf
sed -i 's/port=5432/port=5433/g' app.conf
sed -i 's/db_name=test/db_name=production/g' app.conf

# Dogru: Tek geciste birden fazla degisiklik
sed -i 
    -e 's/host=localhost/host=db.internal/g' 
    -e 's/port=5432/port=5433/g' 
    -e 's/db_name=test/db_name=production/g' 
    app.conf

-e parametresi birden fazla expression zinciri oluşturmanıza izin verir. Dosya yalnızca bir kez okunup bir kez yazılır, yedek de sadece bir kez oluşturulur.

Yedek Stratejileri: Sadece .bak Koymak Yetmez

Yerinde düzenleme yapıyorsanız yedekleme konusunda biraz daha sistematik olmanız gerekir. İşte farklı senaryolar için yaklaşımlar:

Versiyon Kontrollü Yedekleme

#!/bin/bash

guvenli_sed() {
    local dosya="$1"
    shift
    local yedek_dizin="/var/backup/config/$(dirname $dosya)"

    # Yedek dizini olustur
    mkdir -p "$yedek_dizin"

    # Mevcut halini versiyonla yedekle
    cp "$dosya" "${yedek_dizin}/$(basename $dosya).$(date +%Y%m%d_%H%M%S).$(whoami)"

    # sed degisikliklerini uygula
    sed -i "$@" "$dosya"

    echo "Degisiklik uygulandi: $dosya"
    echo "Yedek: ${yedek_dizin}/$(basename $dosya)"
}

# Kullanim
guvenli_sed /etc/myapp/config.ini 
    -e 's/log_level=DEBUG/log_level=INFO/g' 
    -e 's/max_connections=10/max_connections=50/g'

Bu fonksiyon ile sed -i komutu etrafına bir güvenlik katmanı sarıyorsunuz. Kim, ne zaman değiştirdi bilgisi .$(whoami) uzantısıyla yedeğe gömülüyor. Ortak kullanılan sunucularda bu bilgi paha biçilmez.

Değişiklik Öncesi Doğrulama Akışı

#!/bin/bash

DOSYA="/etc/postgresql/14/main/postgresql.conf"
PATTERN='max_connections = 100'
YENI='max_connections = 200'

# 1. Dosya var mi?
if [ ! -f "$DOSYA" ]; then
    echo "HATA: Dosya bulunamadi: $DOSYA"
    exit 1
fi

# 2. Pattern dosyada mevcut mu?
if ! grep -qF "$PATTERN" "$DOSYA"; then
    echo "UYARI: Pattern bulunamadi, degisiklik yapilmadi"
    echo "Aranan: $PATTERN"
    exit 0
fi

# 3. Kac yerde eslesme var? Beklenenden fazla ise dur
ESLESME_SAYISI=$(grep -cF "$PATTERN" "$DOSYA")
if [ "$ESLESME_SAYISI" -gt 1 ]; then
    echo "UYARI: Pattern $ESLESME_SAYISI yerde eslesli, beklenen 1"
    echo "Devam etmek istiyor musunuz? (e/h)"
    read -r cevap
    [ "$cevap" != "e" ] && exit 1
fi

# 4. Degisikligi once goster (dry run)
echo "Yapilacak degisiklik:"
sed "s/$PATTERN/$YENI/g" "$DOSYA" | diff "$DOSYA" - | head -20

echo "Onayliyor musunuz? (e/h)"
read -r onay

if [ "$onay" = "e" ]; then
    sed -i.$(date +%Y%m%d_%H%M%S) "s/$PATTERN/$YENI/g" "$DOSYA"
    echo "Tamamlandi."
fi

Bu script üretim ortamında PostgreSQL parametresi değiştirirken gerçekten kullandığım bir yaklaşıma benziyor. Dry run adımı kritik: değişikliği uygulamadan önce ne olacağını görmek, geri dönülemez hatalardan korur.

Dikkat Edilmesi Gereken Kenar Durumlar

Symlink Sorunu

Daha önce bahsettiğim gibi sed -i arka planda geçici dosya yaratıp rename eder. Bu davranış symlink’leri takip etmez, symlink’i kırar.

# Bu symlink'i kirar
ln -s /etc/uygulama/prod.conf /etc/uygulama/current.conf
sed -i 's/foo/bar/g' /etc/uygulama/current.conf
# Artik current.conf gercek bir dosya, symlink degil

# Symlink korumak icin
sed 's/foo/bar/g' /etc/uygulama/current.conf > /tmp/current.conf.tmp
cat /tmp/current.conf.tmp > /etc/uygulama/current.conf
rm /tmp/current.conf.tmp

cat ile yönlendirme yaklaşımı mevcut inode’u korur, bu da symlink zincirini kırmaz.

Özel Karakterler ve Delimiter Seçimi

URL’ler veya yol ifadeleri içeren pattern’leri değiştirirken / karakteri sorun çıkarır. Delimiter değiştirin:

# Bu calismaz
sed -i 's/http://old.domain.com/http://new.domain.com/g' config.conf

# Farkli delimiter kullanin
sed -i 's|http://old.domain.com|http://new.domain.com|g' config.conf

# Ya da # ile
sed -i 's#/usr/local/old#/usr/local/new#g' script.sh

Büyük Dosyalarda Performans

Yüzlerce megabyte’lık log dosyaları veya büyük veri dosyaları üzerinde sed -i çalıştırırken geçici dosya için yeterli disk alanı olduğundan emin olun. sed -i geçici dosyayı genellikle aynı dizinde oluşturur.

# Disk durumunu kontrol et
df -h $(dirname /path/to/buyuk_dosya)

# Gecici dizini belirterek calistir (GNU sed destekler)
TMPDIR=/dizin/yeterli/alan sed -i 's/eski/yeni/g' buyuk_dosya.txt

Yedekleri Temizleme

Sistematik yedek alıyorsanız temizleme stratejiniz de olmalı. Yedekler birikir ve disk dolar.

#!/bin/bash

# 30 gundan eski config yedeklerini temizle
find /var/backup/config -name "*.bak" -mtime +30 -delete
find /var/backup/config -name "*.2[0-9][0-9][0-9][0-1][0-9][0-3][0-9]*" -mtime +30 -delete

# Belirli dizindeki tum .bak dosyalarini listele
find /etc -name "*.bak" -printf "%st%pn" | sort -n | awk '{print $2}'

# Toplam yer kaplayan yedekler
du -sh /var/backup/config/

Yedek temizliğini de bir cron job’a bağlamak akıllıca bir yaklaşım. Aksi halde bir gün diskten doldu alarmıyla uyanabilirsiniz.

Pratik Notlar ve Son Düşünceler

sed -i ile çalışırken öğrendiğim bazı pratik kurallar şunlar:

  • Önce grep ile doğrula: Değişikliği uygulamadan önce pattern’in dosyada olduğunu, doğru sayıda eşleşme verdiğini kontrol et.
  • Karmaşık değişiklikler için önce stdout’a bak: -i olmadan çalıştır, çıktı beklediğin gibiyse -i ekle.
  • Prodüksiyon değişikliklerinde tarih damgalı yedek kullan: .bak uzantısı aynı dosyayı iki kez değiştirince ilk yedeği ezer, tarih damgası bunu engeller.
  • Script içinde sed -i kullanıyorsan hata yönetimi ekle: set -e veya her komut sonrası || { echo "Hata"; exit 1; } eklemek sizi kurtarır.
  • macOS ve Linux arasındaki -i farkını unutma: Taşınabilir script yazıyorsan sed -i.bak sözdizimini kullan, her ikisinde de çalışır.

sed konusunda uzmanlaşmak istiyorsanız şunu öneririm: ilk başta -i parametresini mümkün olduğunca az kullanın. Önce standart çıktıya yazarak komutunuzun ne yaptığını anlayın, sonra -i ekleyin. Bu alışkanlık ileride ciddi hatalardan korur.

Yerinde düzenleme güçlü bir araçtır, ama bu güç sorumluluk gerektirir. Üretim ortamında yaptığınız her sed -i işlemi bir değişiklik yönetimi sürecinin parçası olmalıdır: neyi neden değiştirdiğinizi belgeleyin, yedek alın, doğrulayın ve gerekirse geri dönün. Bu adımları atlamak kısa vadede zaman kazandırır gibi görünür, uzun vadede ise sizi gece yarısı incident’larının ortasında bırakır.

Sonuç

sed -i ve yedekleme stratejileri bir teknik konu gibi görünse de aslında özünde operasyonel olgunluk meselesidir. Hangi komutun ne yaptığını bilmek, onu güvenli kullanmaktan farklı bir beceridir. Bu yazıda anlattığım yaklaşımlar yıllar içinde gerçek ortamlarda şekillenmiş pratikler.

Özetle:

  • sed -i.uzanti: Yerinde düzenleme için otomatik yedek oluşturmanın en temiz yolu
  • -e ile çoklu expression: Tek geçişte birden fazla değişiklik için vazgeçilmez
  • Symlink farkındalığı: sed -i‘nin symlink davranışını bilin, gerektiğinde alternatif yöntem kullanın
  • Doğrulama akışı: Dry run, pattern kontrolü ve diff eklemek prodüksiyon güvenliğinin temelidir
  • Yedek temizliği: Yedek alıyorsanız temizleme planınız da olsun

Yorumlarınızı ve kendi kullandığınız yaklaşımları paylaşabilirsiniz. Özellikle büyük ölçekli ortamlarda sed -i yönetimi için farklı çözümler keşfetmek hep ilgi çekici oluyor.

Bir yanıt yazın

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