Yedeklerini Koruyarak Dosya Değiştirme: sponge ve moreutils Araçlarının Gerçek Hayat Kullanımı
Bir dosyayı okuyup değiştirip aynı dosyaya geri yazmak… Kulağa basit geliyor, değil mi? Ama bu masum görünen işlem, yanlış yapıldığında veriyi yerle bir edebilir. sed -i alışkanlığınız varsa ve ne yaptığını tam anlamıyla düşünmeden kullanıyorsanız, bu yazı tam size göre. Bugün sponge aracını ve beraber geldiği moreutils paketini ele alacağız. Sadece teorik değil, gerçekten üretimde işe yarayan senaryolarla.
Sorun Nerede Başlıyor?
Shell pipeline’larında klasik bir tuzak vardır. Diyelim ki bir dosyadan belirli satırları filtreleyip aynı dosyaya kaydetmek istiyorsunuz:
grep "ERROR" /var/log/app.log > /var/log/app.log
Bu komutu çalıştırdığınızda ne olur? Shell önce > ile dosyayı açar ve sıfırlar, sonra grep çalışmaya başlar. Grep okumak istediği dosyayı boş bulur. Sonuç: sıfır baytlık, bomboş bir log dosyası. Üretimde bunu yaşayan birçok meslektaşım oldu, inanın.
sed ile de benzer durumlar yaşanabilir:
sed 's/old_value/new_value/g' config.txt > config.txt
Aynı sorun. Shell önce config.txt‘yi truncate eder, sed boş dosyayı okur. -i flag’i bu sorunu çözer ama kendi başka sorunları vardır. GNU sed ile BSD sed’in -i davranışları farklıdır, bu da cross-platform scriptlerde başınızı ağrıtır.
İşte tam bu noktada sponge devreye giriyor.
sponge Nedir ve Ne Yapar?
sponge adı üstünde, sünger gibi tüm stdin’i içine çeker, input tamamen bittikten sonra belirtilen dosyaya yazar. Bu sayede input ve output aynı dosya olduğunda veri kaybı yaşanmaz.
moreutils paketinin bir parçasıdır. Ubuntu/Debian’da kurulum şöyle:
sudo apt-get install moreutils
RHEL/CentOS/Rocky için:
sudo dnf install moreutils
# veya EPEL reposundan:
sudo yum install epel-release && sudo yum install moreutils
Kullanımı son derece sade:
grep "ERROR" /var/log/app.log | sponge /var/log/app.log
sponge önce tüm grep çıktısını belleğe alır, grep tamamlanınca dosyaya yazar. Orijinal dosya, pipeline tamamlanana kadar dokunulmaz kalır. Evet, bu kadar basit.
moreutils Paketi: Gizli Kalmış Araçlar Hazinesi
sponge tek başına yeterince değerli, ama moreutils paketi başka güzel sürprizler de içeriyor. Yükleyince ne geliyor bir bakalım:
- sponge: stdin’i tampon belleğe alıp dosyaya yazar
- ts: pipeline çıktısına zaman damgası ekler
- vipe: pipeline ortasına editör açar
- errno: hata kodlarını açıklar
- ifdata: ağ arayüzü bilgilerini ayrıştırır
- ifne: stdin boş değilse komutu çalıştırır
- isutf8: UTF-8 geçerliliği kontrol eder
- lckdo: dosya kilidi alarak komut çalıştırır
- mispipe: pipeline’da ilk başarısız komutun exit code’unu döner
- parallel: komutları paralel çalıştırır
- pee: tee gibi ama pipeline’ları paralel besler
- combine: dosya listelerini mantıksal operatörlerle birleştirir
- chronic: sadece hata varsa çıktı verir (cron için biçilmiş kaftan)
- vidir: dizin içeriğini editörde düzenlemenize izin verir
- zrun: sıkıştırılmış dosyalara otomatik çalıştırma
Bugün odaklanacağımız sponge ama ts ve chronic‘e de değineceğim, çünkü pratikte birlikte çok sık kullanılıyorlar.
Gerçek Hayat Senaryoları
Senaryo 1: Konfigürasyon Dosyası Güvenli Güncelleme
Prod ortamında bir uygulama konfigürasyon dosyasında toplu değişiklik yapmanız gerekiyor. Klasik yöntem sed -i ama bu dosyanın yedeğini almak da istiyorsunuz:
# Değiştirmeden önce otomatik yedek al, sonra güvenle yaz
cp /etc/myapp/config.yaml /etc/myapp/config.yaml.bak.$(date +%Y%m%d_%H%M%S)
sed 's/db_host: localhost/db_host: 10.0.1.50/g' /etc/myapp/config.yaml |
sponge /etc/myapp/config.yaml
sed -i.bak de yedek alır, ama platform bağımlıdır. GNU sed’de -i.bak çalışırken macOS’taki BSD sed’de -i '.bak' şeklinde yazılması gerekir. sponge kullanan bu yöntem her ortamda aynı davranır.
Senaryo 2: JSON Konfigürasyonlarını Toplu İşleme
Mikroservis mimarisinde onlarca config.json dosyasını güncellemeniz gerektiğinde:
#!/bin/bash
# Tüm servis config'lerinde API endpoint'ini güncelle
find /opt/services -name "config.json" | while read config_file; do
echo "Processing: $config_file"
# Yedek al
cp "$config_file" "${config_file}.backup_$(date +%Y%m%d)"
# jq ile parse edip güncelle, sponge ile güvenle yaz
jq '.api.endpoint = "https://api.newdomain.com/v2"' "$config_file" |
sponge "$config_file"
done
echo "Tüm config'ler güncellendi."
jq ... config.json > config.json yaparsanız jq hiçbir şey okuyamadan dosyayı boşaltırsınız. sponge olmadan bu scripti yazmak için geçici dosya kullanmak zorunda kalırdınız.
Senaryo 3: Log Rotasyon ve Temizlik
Uygulamanın ürettiği log dosyasından eski ve gereksiz girdileri temizleyen, sadece son 7 günün loglarını tutan bir script:
#!/bin/bash
LOG_FILE="/var/log/myapp/application.log"
RETENTION_DAYS=7
# Tarihe göre filtrele ve aynı dosyaya geri yaz
# grep -v veya awk ile N günden eski satırları at
cutoff_date=$(date -d "$RETENTION_DAYS days ago" '+%Y-%m-%d')
awk -v cutoff="$cutoff_date" '
{
# Log formatı: 2024-01-15 14:30:22 - mesaj
if ($1 >= cutoff) print
}
' "$LOG_FILE" | sponge "$LOG_FILE"
echo "Log temizlendi. Kalan satır: $(wc -l < $LOG_FILE)"
Senaryo 4: ts ile Pipeline Zaman Damgalama
Bu senaryo doğrudan sponge ile ilgili değil ama moreutils‘den ts aracını göstermek istedim çünkü monitoring scriptlerinde çok işe yarıyor:
#!/bin/bash
# Uzun süren deployment scriptinin adımlarını zaman damgasıyla logla
./deploy.sh 2>&1 | ts '[%Y-%m-%d %H:%M:%S]' | tee /var/log/deploys/deploy_$(date +%Y%m%d).log
Çıktı şöyle görünür:
[2024-01-15 14:30:01] Starting deployment...
[2024-01-15 14:30:05] Pulling Docker images...
[2024-01-15 14:30:42] Running migrations...
Her satırın tam olarak ne zaman çalıştığını bilmek, incident sonrası analizde hayat kurtarır.
Senaryo 5: chronic ile Cron Job Temizliği
Cron’un en büyük sorunlarından biri: her başarılı çalışmada da mail/log atar. chronic sadece bir şeyler yanlış giderse çıktı üretir:
# /etc/cron.d/db-backup
0 2 * * * root chronic /usr/local/bin/db_backup.sh
chronic sayesinde db_backup.sh başarıyla çalışırsa hiçbir çıktı görmezsiniz. Hata olursa tüm çıktıyı alırsınız. Artık cron maili gelen kutunuzu çöp kutusuna çevirmez.
Senaryo 6: Büyük Dosyalarda Güvenli In-Place Düzenleme
Birkaç gigabyte’lık CSV dosyasında header’ı değiştirmeniz ya da belirli kolonları düzenlemeniz gerekiyor:
#!/bin/bash
LARGE_CSV="/data/exports/sales_2024.csv"
# Header satırını düzelt ve alanları normalize et
# sponge dosyayı tamamen okuduktan sonra yazar
# Bu sayede büyük dosyalarda race condition olmaz
{
head -1 "$LARGE_CSV" | sed 's/SATIS_TARIHI/sale_date/g; s/MUSTERI_ID/customer_id/g'
tail -n +2 "$LARGE_CSV"
} | sponge "$LARGE_CSV"
Burada dikkat edin: sponge tüm dosyayı belleğe alır. Gerçekten devasa dosyalarda (50GB+) bu sorun yaratabilir. O boyutlarda geçici dosya yaklaşımı daha güvenli olur. Ama birkaç gigabyte için sponge rahatlıkla kullanılabilir.
Senaryo 7: Kubernetes ve Terraform Konfigürasyon Yönetimi
DevOps tarafında environment bazlı config güncellemeleri çok yaygın:
#!/bin/bash
# Helm values dosyasını environment'a göre güncelle
ENV="${1:-staging}"
VALUES_FILE="helm/values-${ENV}.yaml"
if [ ! -f "$VALUES_FILE" ]; then
echo "Values dosyası bulunamadı: $VALUES_FILE"
exit 1
fi
# Git'e göre mevcut commit hash'i al
COMMIT_HASH=$(git rev-parse --short HEAD)
IMAGE_TAG="${ENV}-${COMMIT_HASH}"
echo "Image tag güncelleniyor: $IMAGE_TAG"
# yq ile YAML güncelle, sponge ile güvenle yaz
yq eval ".image.tag = "$IMAGE_TAG"" "$VALUES_FILE" |
sponge "$VALUES_FILE"
# Değişikliği kontrol et
echo "Güncel tag:"
yq eval '.image.tag' "$VALUES_FILE"
sponge’ın İzinleri Nasıl Yönetir?
Bu önemli bir detay. sponge varsayılan olarak orijinal dosyanın izinlerini korur. Yani dosya 640 ise, sponge yazdıktan sonra hala 640 olur. Ama eğer dosya yoksa, sponge yeni dosyayı umask’a göre oluşturur.
# Test edelim
echo "test içerik" > /tmp/testfile
chmod 640 /tmp/testfile
ls -la /tmp/testfile
# -rw-r----- 1 user group 13 Jan 15 14:30 /tmp/testfile
cat /tmp/testfile | sed 's/içerik/değiştirildi/g' | sponge /tmp/testfile
ls -la /tmp/testfile
# -rw-r----- 1 user group 18 Jan 15 14:31 /tmp/testfile
# İzinler korundu!
sed -i da benzer şekilde davranır ama bazı implementasyonlarda yeni bir dosya oluşturup atomic rename yapar, bu da sembolik linklerde beklenmedik davranışlara yol açabilir. sponge daha öngörülü davranır.
Pipe Chain’lerde sponge’ı Doğru Konumlandırmak
sponge her zaman pipeline’ın en sonunda yer alır ve dosya argümanı alır. Bunu karıştırmak mümkün:
# YANLIŞ: sponge ortada kullanılamaz
cat file.txt | sponge | sed 's/old/new/g' > file.txt # İşe yaramaz
# DOĞRU: Pipeline'ın sonu
cat file.txt | sed 's/old/new/g' | sponge file.txt
# DOĞRU: Birden fazla işlem zinciri
cat file.txt |
sed 's/old_host/new_host/g' |
grep -v "^#" |
sort -u |
sponge file.txt
sponge ve Geçici Dosya Yaklaşımının Karşılaştırması
Geçici dosya yöntemi de güvenlidir ama daha fazla kod gerektirir:
# Geçici dosya yaklaşımı
TMPFILE=$(mktemp)
sed 's/old/new/g' config.txt > "$TMPFILE"
mv "$TMPFILE" config.txt
# Hata yönetimi eklenirse daha da uzar...
# sponge yaklaşımı
sed 's/old/new/g' config.txt | sponge config.txt
# Tek satır, temiz, güvenli
Ancak sponge‘ın dezavantajları da var. Büyük dosyalarda belleği zorlayabilir. Yazma başarısız olursa (disk dolu gibi) orijinal dosya zaten tamamen okunmuş olduğundan, teorik olarak veri kaybı riski vardır. Bu nedenle kritik dosyalarda önce yedek almak hala iyi bir pratik.
Pratik Bir Güvenlik Katmanı: sponge + Yedekleme
Üretim ortamında her in-place değişiklik için şu pattern’i kullanıyorum:
#!/bin/bash
# safe_edit.sh - Güvenli dosya düzenleme fonksiyonu
safe_edit() {
local file="$1"
local backup_dir="/var/backups/configs"
local timestamp=$(date +%Y%m%d_%H%M%S)
# Backup dizini yoksa oluştur
mkdir -p "$backup_dir"
# Yedek al
cp "$file" "$backup_dir/$(basename $file).${timestamp}.bak"
echo "Yedek alındı: $backup_dir/$(basename $file).${timestamp}.bak"
# sponge ile oku ve bekle, stdin'den yazar
sponge "$file"
echo "Dosya güncellendi: $file"
}
# Kullanımı:
# sed 's/old/new/g' /etc/app/config | safe_edit /etc/app/config
Bu fonksiyonu .bashrc veya bir shared script kütüphanesine ekleyince her yerde kullanılabilir hale geliyor.
moreutils’in Diğer Araçlarına Hızlı Bakış
Konudan çok uzaklaşmadan birkaç aracı daha ele alalım:
ifne – Stdin boş değilse çalıştır:
# Disk kullanımı %90'ı geçerse uyarı gönder
df -h | awk '$5 > 90 {print $0}' | ifne mail -s "DISK ALARM" [email protected]
combine – Dosya kümelerini birleştir:
# A'da olup B'de olmayan IP'leri bul
combine whitelist.txt not blacklist.txt
errno – Hata kodlarını anlamlandır:
errno 28
# 28: No space left on device
Sistematik debugging yaparken errno aracı gereksiz Google aramalarını önlüyor.
Sonuç
sponge küçük bir araç ama çözdüğü problem gerçek. Shell pipeline’larında aynı dosyayı okuyup yazma ihtiyacı her gün karşıma çıkıyor: konfigürasyon güncellemeleri, log temizlikleri, toplu veri dönüşümleri. Her seferinde geçici dosya yaratıp silmek hem kod kalitesini düşürüyor hem de hata yönetimini karmaşıklaştırıyor.
moreutils paketi genel olarak “neden standart utils içinde yok bunlar?” dedirten araçlar barındırıyor. chronic ile cron gürültüsünü azaltmak, ts ile log zaman damgalarını otomatikleştirmek, sponge ile güvenli in-place yazma yapmak… Bunların hepsi prod ortamında somut fark yaratıyor.
Eğer hala sed -i kullanıp platform uyumluluk sorunlarıyla uğraşıyorsanız, ya da command > file tuzağına bir kez bile düştüyseniz, moreutils‘i hemen kurun. İki dakika yatırım, uzun vadeli rahatlık. Prod’da “neden dosya boş kaldı” sorusunu bir daha sormak istemiyorsanız, sponge‘ı araç kutunuza ekleyin.
