Bash’te Dosya ve Dizin İşlemleri: Test Operatörleri Rehberi

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.

Yorum yapın