Bash scriptlerinde en çok hata yapılan yerlerden biri, dosya ve dizinlerin varlığını veya durumunu kontrol etmeden doğrudan işlem yapmaya çalışmaktır. “Dosya yok” hatası alan bir script, en iyi ihtimalle sessizce çöker; en kötü ihtimalle production verilerini siler. Test operatörleri, bu tür felaketleri önlemenin temel aracıdır ve her ciddi Bash geliştiricisinin içgüdüsel olarak kullanması gereken yapılardır.
Test Operatörleri Nedir?
Bash’te test operatörleri, belirli koşulları kontrol eden ve sonuç olarak true (0) veya false (1) döndüren ifadelerdir. Dosya ve dizin işlemleri için kullanılan bu operatörler, [ ] veya [[ ]] blokları içinde çalışır. İkisi arasındaki farka kısaca değinelim: [ ] POSIX uyumludur ve daha eski sistemlerde çalışır; [[ ]] ise Bash’e özgü bir yapıdır, daha güvenli ve güçlüdür. Özellikle string karşılaştırmalarında ve regex işlemlerinde [[ ]] tercih edilir.
Dosya test operatörleri şu kategorilere ayrılır:
- Varlık kontrolleri: Dosya veya dizin var mı?
- Tür kontrolleri: Bu bir dizin mi, sembolik link mi, block device mi?
- İzin kontrolleri: Okunabilir mi, yazılabilir mi, çalıştırılabilir mi?
- Boyut ve içerik kontrolleri: Dosya boş mu, boyutu sıfırdan büyük mü?
- Zaman damgası karşılaştırmaları: Hangi dosya daha yeni?
Temel Dosya Test Operatörleri
Varlık ve Tür Kontrolleri
En sık kullanılan operatörleri ve ne işe yaradıklarını açıklayalım:
-e: Dosya veya dizin mevcut mu? (her türlü dosya için) -f: Düzenli bir dosya mı? (dizin veya özel dosya değil) -d: Dizin mi? -L: Sembolik link mi? -b: Block device mi? (disk gibi) -c: Character device mi? (terminal gibi) -p: Named pipe (FIFO) mi? -S: Socket mi?
#!/bin/bash
DOSYA="/etc/nginx/nginx.conf"
DIZIN="/var/log/nginx"
# Temel varlık kontrolü
if [ -e "$DOSYA" ]; then
echo "Dosya veya dizin mevcut: $DOSYA"
fi
# Sadece düzenli dosya kontrolü
if [ -f "$DOSYA" ]; then
echo "Bu bir düzenli dosya"
fi
# Dizin kontrolü
if [ -d "$DIZIN" ]; then
echo "Log dizini mevcut"
else
echo "Log dizini bulunamadı, oluşturuluyor..."
mkdir -p "$DIZIN"
fi
# Sembolik link kontrolü
if [ -L "/etc/nginx/sites-enabled/default" ]; then
echo "Default site etkin (symlink mevcut)"
fi
İzin Kontrolleri
-r: Okunabilir mi? -w: Yazılabilir mi? -x: Çalıştırılabilir mi? -u: SUID biti set edilmiş mi? -g: SGID biti set edilmiş mi? -k: Sticky bit set edilmiş mi?
#!/bin/bash
SCRIPT="/usr/local/bin/backup.sh"
KONFIG="/etc/app/config.yml"
# Çalıştırılabilirlik kontrolü
if [ ! -x "$SCRIPT" ]; then
echo "Script çalıştırılabilir değil, izin veriliyor..."
chmod +x "$SCRIPT"
fi
# Yazma izni kontrolü
if [ ! -w "$KONFIG" ]; then
echo "HATA: $KONFIG dosyasına yazma izniniz yok!"
exit 1
fi
# Okuma izni ile dosyayı işleme
if [ -r "$KONFIG" ]; then
while IFS= read -r satir; do
echo "Konfig satırı: $satir"
done < "$KONFIG"
fi
Boyut ve İçerik Kontrolleri
-s: Dosya boyutu sıfırdan büyük mü? (dosya boş değil mi?) -z ile karıştırmayın; -z string uzunluğu kontrolü içindir.
#!/bin/bash
LOG_DOSYASI="/var/log/uygulama/error.log"
# Dosya boş değil mi?
if [ -s "$LOG_DOSYASI" ]; then
echo "Hata logu dolu, inceleniyor..."
tail -50 "$LOG_DOSYASI" | mail -s "Uygulama Hata Logu" [email protected]
else
echo "Hata logu boş, sistem sağlıklı görünüyor"
fi
Zaman Damgası Karşılaştırmaları
-nt: (newer than) Sol dosya sağdan daha yeni mi? -ot: (older than) Sol dosya sağdan daha eski mi? -ef: (equal file) Her iki dosya aynı inode’u paylaşıyor mu? (hardlink kontrolü)
#!/bin/bash
KAYNAK="/etc/app/config.yml"
YEDEK="/backup/config.yml.bak"
# Kaynak dosya yedekten daha yeni mi?
if [ "$KAYNAK" -nt "$YEDEK" ]; then
echo "Konfig değişmiş, yedek güncelleniyor..."
cp "$KAYNAK" "$YEDEK"
echo "Yedek güncellendi: $(date)"
else
echo "Yedek güncel, işlem gerekmiyor"
fi
# Hardlink kontrolü
if [ "$KAYNAK" -ef "$YEDEK" ]; then
echo "Bu dosyalar aynı inode'u paylaşıyor (hardlink)"
fi
Gerçek Dünya Senaryoları
Senaryo 1: Otomatik Yedekleme Scripti
Bir web sunucusunda her gece çalışan yedekleme scripti yazalım. Bu script, log rotasyonu yapmadan önce gerekli kontrolleri gerçekleştirir.
#!/bin/bash
# /usr/local/bin/gece-yedek.sh
# Her gece 02:00'de cron ile çalışır
set -euo pipefail
TARIH=$(date +%Y%m%d)
KAYNAK_DIZIN="/var/www/html"
YEDEK_DIZIN="/backup/web"
YEDEK_DOSYA="$YEDEK_DIZIN/web-backup-$TARIH.tar.gz"
LOG_DOSYASI="/var/log/yedekleme/backup-$TARIH.log"
# Log dizini var mı?
if [ ! -d "/var/log/yedekleme" ]; then
mkdir -p "/var/log/yedekleme"
fi
# Loglama fonksiyonu
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" >> "$LOG_DOSYASI"
echo "$1"
}
# Kaynak dizin kontrolü
if [ ! -d "$KAYNAK_DIZIN" ]; then
log "HATA: Kaynak dizin mevcut değil: $KAYNAK_DIZIN"
exit 1
fi
if [ ! -r "$KAYNAK_DIZIN" ]; then
log "HATA: Kaynak dizin okunamıyor: $KAYNAK_DIZIN"
exit 1
fi
# Yedek dizini oluştur (yoksa)
if [ ! -d "$YEDEK_DIZIN" ]; then
log "Yedek dizini oluşturuluyor: $YEDEK_DIZIN"
mkdir -p "$YEDEK_DIZIN"
fi
# Yedek dizinine yazma izni var mı?
if [ ! -w "$YEDEK_DIZIN" ]; then
log "HATA: Yedek dizinine yazma izni yok: $YEDEK_DIZIN"
exit 1
fi
# Aynı tarihte yedek zaten var mı?
if [ -f "$YEDEK_DOSYA" ]; then
log "UYARI: Bu tarih için yedek zaten mevcut: $YEDEK_DOSYA"
log "Eski yedek siliniyor..."
rm "$YEDEK_DOSYA"
fi
# Yedekleme işlemi
log "Yedekleme başlıyor..."
tar -czf "$YEDEK_DOSYA" -C "$(dirname $KAYNAK_DIZIN)" "$(basename $KAYNAK_DIZIN)" 2>> "$LOG_DOSYASI"
# Yedek oluştu mu ve boyutu var mı?
if [ -f "$YEDEK_DOSYA" ] && [ -s "$YEDEK_DOSYA" ]; then
BOYUT=$(du -sh "$YEDEK_DOSYA" | cut -f1)
log "Yedekleme başarılı! Dosya: $YEDEK_DOSYA, Boyut: $BOYUT"
else
log "HATA: Yedek dosyası oluşturulamadı veya boş!"
exit 1
fi
# 30 günden eski yedekleri temizle
log "Eski yedekler temizleniyor..."
find "$YEDEK_DIZIN" -name "web-backup-*.tar.gz" -mtime +30 -delete
log "Temizlik tamamlandı"
Senaryo 2: Konfig Dosyası Yöneticisi
Bir deployment scripti, uygulama ayarlarını ortama göre yönetmeli. Doğru konfig dosyasının yüklü olup olmadığını kontrol etmek kritik önem taşır.
#!/bin/bash
# deploy-konfig.sh
ORTAM="${1:-production}"
KONFIG_DIR="/etc/myapp"
SABLONLAR_DIR="/opt/myapp/templates"
kontrol_et() {
local dosya="$1"
local aciklama="$2"
if [ ! -e "$dosya" ]; then
echo "HATA: $aciklama bulunamadı: $dosya"
return 1
fi
if [ -L "$dosya" ]; then
local gercek_yol
gercek_yol=$(readlink -f "$dosya")
if [ ! -f "$gercek_yol" ]; then
echo "HATA: $aciklama sembolik linki kırık: $dosya -> $gercek_yol"
return 1
fi
echo "OK: $aciklama (symlink -> $gercek_yol)"
elif [ -f "$dosya" ]; then
if [ ! -s "$dosya" ]; then
echo "UYARI: $aciklama mevcut ama boş: $dosya"
return 1
fi
echo "OK: $aciklama mevcut ve dolu"
fi
return 0
}
# Gerekli dizinleri kontrol et
for dizin in "$KONFIG_DIR" "$SABLONLAR_DIR"; do
if [ ! -d "$dizin" ]; then
echo "Dizin oluşturuluyor: $dizin"
mkdir -p "$dizin"
fi
done
# Ortama özgü konfig dosyasını kontrol et
ORTAM_KONFIG="$SABLONLAR_DIR/$ORTAM.yml"
if ! kontrol_et "$ORTAM_KONFIG" "$ORTAM konfig şablonu"; then
echo "Dağıtım durduruluyor."
exit 1
fi
# Mevcut konfiği yedekle
AKTIF_KONFIG="$KONFIG_DIR/config.yml"
if [ -f "$AKTIF_KONFIG" ]; then
YEDEK="$KONFIG_DIR/config.yml.$(date +%Y%m%d%H%M%S).bak"
cp "$AKTIF_KONFIG" "$YEDEK"
echo "Mevcut konfig yedeklendi: $YEDEK"
fi
# Yeni konfiği devreye al
cp "$ORTAM_KONFIG" "$AKTIF_KONFIG"
chmod 640 "$AKTIF_KONFIG"
echo "Konfig başarıyla dağıtıldı: $ORTAM -> $AKTIF_KONFIG"
Senaryo 3: Servis Sağlık Kontrolü
Birden fazla servisin PID dosyalarını ve socket dosyalarını kontrol eden bir monitoring scripti:
#!/bin/bash
# /usr/local/bin/servis-kontrol.sh
SERVISLER=("nginx" "postgresql" "redis" "myapp")
SORUNLU_SERVISLER=()
RAPOR=""
servis_kontrol() {
local servis="$1"
local pid_dosyasi="/var/run/$servis/$servis.pid"
local socket_dosyasi="/var/run/$servis/$servis.sock"
local durum="SORUN"
RAPOR+="=== $servis ===n"
# PID dosyası kontrolü
if [ -f "$pid_dosyasi" ] && [ -s "$pid_dosyasi" ]; then
local pid
pid=$(cat "$pid_dosyasi")
if [ -d "/proc/$pid" ]; then
RAPOR+=" PID: $pid (çalışıyor)n"
durum="OK"
else
RAPOR+=" PID dosyası var ama process çalışmıyor! PID: $pidn"
fi
elif [ -f "$pid_dosyasi" ] && [ ! -s "$pid_dosyasi" ]; then
RAPOR+=" PID dosyası boş!n"
else
RAPOR+=" PID dosyası bulunamadı: $pid_dosyasin"
fi
# Socket dosyası kontrolü (varsa)
if [ -S "$socket_dosyasi" ]; then
RAPOR+=" Socket: mevcut ve aktifn"
elif [ -e "$socket_dosyasi" ] && [ ! -S "$socket_dosyasi" ]; then
RAPOR+=" UYARI: Socket yolu mevcut ama socket değil!n"
fi
if [ "$durum" = "SORUN" ]; then
SORUNLU_SERVISLER+=("$servis")
fi
}
for servis in "${SERVISLER[@]}"; do
servis_kontrol "$servis"
done
echo -e "$RAPOR"
if [ ${#SORUNLU_SERVISLER[@]} -gt 0 ]; then
echo "SORUNLU SERVİSLER: ${SORUNLU_SERVISLER[*]}"
# Alarm gönder
echo -e "$RAPOR" | mail -s "[ALARM] Servis Sorunu Tespit Edildi" [email protected]
exit 1
fi
echo "Tüm servisler sağlıklı çalışıyor."
exit 0
Sık Yapılan Hatalar ve Çözümleri
Tırnak İşareti Sorunu
En yaygın hatalardan biri, değişkenleri tırnak içine almamaktır. Dosya adında boşluk varsa script patlar.
#!/bin/bash
# YANLIS - dosya adında boşluk varsa hata verir
DOSYA="/home/kullanici/my documents/rapor.pdf"
if [ -f $DOSYA ]; then # Hata! Boşluk nedeniyle parse edilemiyor
echo "Dosya var"
fi
# DOGRU - her zaman çift tırnak kullan
if [ -f "$DOSYA" ]; then
echo "Dosya var"
fi
# Daha da güvenli: [[ ]] kullan
if [[ -f "$DOSYA" ]]; then
echo "Dosya var"
fi
Race Condition Problemi
Dosyayı kontrol edip sonra işlem yapmak arasında geçen sürede dosya silinebilir. Kritik sistemlerde bu durumu göz önünde bulundurmalısın.
#!/bin/bash
# Daha güvenli yaklaşım: işlemi yap, hatayı yakala
DOSYA="/tmp/kritik-islem.lock"
# Lock dosyası oluştur (atomic operasyon)
if ( set -o noclobber; echo "$$" > "$DOSYA" ) 2>/dev/null; then
echo "Lock alındı, işlem başlıyor..."
# Temizlik için trap kur
trap "rm -f $DOSYA; exit" INT TERM EXIT
# İşlemler burada...
sleep 5
rm -f "$DOSYA"
trap - INT TERM EXIT
echo "İşlem tamamlandı, lock serbest bırakıldı"
else
MEVCUT_PID=$(cat "$DOSYA" 2>/dev/null)
echo "İşlem zaten çalışıyor! PID: $MEVCUT_PID"
exit 1
fi
-e vs -f Karışıklığı
-e operatörü sembolik linkleri takip eder ve hedef dosya yoksa false döner. Bu önemli bir nüans:
#!/bin/bash
LINK="/etc/nginx/sites-enabled/mysite"
# -e: symlink hedefi var mı?
if [ -e "$LINK" ]; then
echo "Link ve hedefi mevcut"
fi
# -L: sembolik link mi? (hedef olmasa bile)
if [ -L "$LINK" ]; then
echo "Sembolik link mevcut"
if [ ! -e "$LINK" ]; then
echo "UYARI: Link kırık! Hedef dosya bulunamıyor"
# Kırık linkleri bul ve listele
find /etc/nginx/sites-enabled -maxdepth 1 -type l ! -exec test -e {} ; -print
fi
fi
Operatörleri Kombine Kullanmak
Test operatörlerini && (ve) ve || (veya) ile birleştirerek güçlü koşul blokları yazabilirsin:
#!/bin/bash
DIZIN="/data/logs"
MAX_BOYUT=1073741824 # 1GB byte cinsinden
# Birden fazla koşul
if [ -d "$DIZIN" ] && [ -r "$DIZIN" ] && [ -w "$DIZIN" ]; then
echo "Log dizini erişilebilir ve kullanıma hazır"
else
echo "Log dizininde sorun var!"
[ ! -d "$DIZIN" ] && echo " - Dizin mevcut değil"
[ ! -r "$DIZIN" ] && echo " - Okuma izni yok"
[ ! -w "$DIZIN" ] && echo " - Yazma izni yok"
exit 1
fi
# NOT ile olumsuzlama
if [ ! -f "/var/run/myapp.pid" ]; then
echo "Uygulama çalışmıyor, başlatılıyor..."
systemctl start myapp
fi
# Boyut karşılaştırması için başka yöntem
GERCEK_BOYUT=$(du -sb "$DIZIN" | cut -f1)
if [ "$GERCEK_BOYUT" -gt "$MAX_BOYUT" ]; then
echo "Log dizini 1GB'ı aştı! Temizlik gerekiyor."
find "$DIZIN" -name "*.log" -mtime +7 -delete
fi
find ile Test Operatörlerini Entegre Kullanmak
find komutu kendi test operatörlerine sahip olsa da Bash test operatörleriyle birlikte çok güçlü kombinasyonlar oluşturulabilir:
#!/bin/bash
# Büyük ve eski dosyaları temizle, ama önce kontrol et
HEDEF_DIZIN="${1:-/tmp}"
LOG="/var/log/temizlik.log"
if [ ! -d "$HEDEF_DIZIN" ]; then
echo "Dizin bulunamadı: $HEDEF_DIZIN"
exit 1
fi
echo "Temizlik başlıyor: $HEDEF_DIZIN" | tee -a "$LOG"
# 100MB'den büyük ve 30 günden eski dosyaları bul
while IFS= read -r -d '' dosya; do
if [ -f "$dosya" ] && [ -w "$dosya" ]; then
BOYUT=$(du -sh "$dosya" | cut -f1)
echo "Siliniyor: $dosya ($BOYUT)" | tee -a "$LOG"
rm "$dosya"
elif [ -f "$dosya" ] && [ ! -w "$dosya" ]; then
echo "Atlandı (yazma izni yok): $dosya" | tee -a "$LOG"
fi
done < <(find "$HEDEF_DIZIN" -type f -size +100M -mtime +30 -print0)
echo "Temizlik tamamlandı" | tee -a "$LOG"
Test Komutunu Farklı Formatlarda Kullanmak
Bash’te test operatörlerini üç farklı şekilde yazabilirsin:
test -f "$DOSYA"– eski POSIX stili[ -f "$DOSYA" ]– köşeli parantez (test ile eşdeğer)[[ -f "$DOSYA" ]]– Bash extended test
#!/bin/bash
DOSYA="/etc/passwd"
# Üç yazım biçimi de aynı işi yapar
test -f "$DOSYA" && echo "test komutu: var"
[ -f "$DOSYA" ] && echo "köşeli parantez: var"
[[ -f "$DOSYA" ]] && echo "çift köşeli parantez: var"
# [[ ]] ile regex kullanımı (sadece bu formatta mümkün)
DOSYA_ADI="backup-20240115.tar.gz"
if [[ "$DOSYA_ADI" =~ ^backup-[0-9]{8}.tar.gz$ ]]; then
echo "Geçerli yedek dosyası formatı"
fi
Sonuç
Dosya ve dizin test operatörleri, Bash scriptlerinin en temel yapı taşlarından birini oluşturur. Sağlam bir script yazmak istiyorsan her dosya işleminden önce mutlaka uygun kontrolü yapmalısın. -f, -d, -e gibi temel operatörler günlük kullanımın büyük bölümünü karşılar; ancak -s, -nt, -L gibi operatörleri da repertuarına kattığında script kaliten ciddi biçimde yükselir.
Pratikte en önemli alışkanlıklar şunlardır: Değişkenleri her zaman çift tırnak içine al, işlem yapmadan önce hedef dizinin yazılabilir olduğunu kontrol et, sembolik linklerde kırık link ihtimalini göz önünde bulundur ve kritik işlemlerde lock mekanizması kullan. Bu dört kural, seni scriptlerle ilgili gece çağrılarından büyük ölçüde koruyacaktır.
Test operatörlerini kapsamlı biçimde kullanan scriptler hem daha güvenli çalışır hem de sorun anında anlamlı hata mesajları üretir. Sonuçta iyi bir sysadmin scriptinin değeri, sadece işi yapmasıyla değil, hatayı doğru teşhis etmesiyle de ölçülür.