Bash’te Döngüler: for, while ve until ile Tekrarlı İşlemler

Sistem yöneticiliğinde zamanın büyük bir kısmı tekrarlı işlemlerle geçer. Yüzlerce sunucuya aynı komutu göndermek, binlerce dosyayı tek tek işlemek ya da belirli koşullar sağlanana kadar bir servisi izlemek… Tüm bunları elle yapmak hem zaman kaybı hem de hata riski demek. İşte burada Bash döngüleri devreye giriyor. for, while ve until döngülerini gerçekten kavradığınızda, saatler süren manuel işleri birkaç satır kodla halledebiliyorsunuz.

for Döngüsü: Bilinen Listeler Üzerinde Gezinmek

for döngüsü, elinizde bir liste ya da aralık olduğunda kullanılır. Kaç iterasyon yapacağınızı önceden biliyorsunuz demektir.

Temel Sözdizimi

for degisken in liste; do
    komutlar
done

En basit örnekle başlayalım. Birkaç sunucuya ping atmak istiyorsunuz:

#!/bin/bash

sunucular="web01 web02 web03 db01 db02"

for sunucu in $sunucular; do
    echo "=== $sunucu kontrol ediliyor ==="
    ping -c 2 -W 1 "$sunucu" > /dev/null 2>&1
    if [ $? -eq 0 ]; then
        echo "$sunucu: ERIŞILEBILIR"
    else
        echo "$sunucu: ERIŞILEMIYOR - Dikkat!"
    fi
done

Bu script birkaç saniyede tüm sunucuları kontrol ediyor. Gerçek hayatta sunucu listesini bir dosyadan okumak daha pratik:

#!/bin/bash

# /etc/monitored_hosts dosyasından sunucu listesini oku
HOSTS_FILE="/etc/monitored_hosts"
LOG_FILE="/var/log/host_check_$(date +%Y%m%d).log"

if [ ! -f "$HOSTS_FILE" ]; then
    echo "Hata: $HOSTS_FILE bulunamadi!" >&2
    exit 1
fi

echo "Host kontrol basliyor: $(date)" | tee -a "$LOG_FILE"

for sunucu in $(cat "$HOSTS_FILE"); do
    # Yorum satırlarını ve boş satırları atla
    [[ "$sunucu" =~ ^#.*$ ]] && continue
    [[ -z "$sunucu" ]] && continue
    
    if ping -c 2 -W 2 "$sunucu" > /dev/null 2>&1; then
        echo "[OK] $sunucu - $(date +%H:%M:%S)" | tee -a "$LOG_FILE"
    else
        echo "[HATA] $sunucu - $(date +%H:%M:%S)" | tee -a "$LOG_FILE"
        # Kritik sunucu down ise mail gönder
        echo "$sunucu erisilemez durumda!" | mail -s "ALERT: $sunucu DOWN" [email protected]
    fi
done

C Stili for Döngüsü

Sayısal aralıklarla çalışırken C stili sözdizimi çok işe yarıyor:

for (( i=1; i<=10; i++ )); do
    echo "Iterasyon: $i"
done

Bunu gerçek bir senaryoya dönüştürelim. Diyelim ki log rotasyon scriptiniz var ve son 30 günün loglarını arşivlemek istiyorsunuz:

#!/bin/bash

LOG_DIR="/var/log/uygulama"
ARSIV_DIR="/backup/logs"
GUN_SAYISI=30

mkdir -p "$ARSIV_DIR"

for (( gun=1; gun<=$GUN_SAYISI; gun++ )); do
    tarih=$(date -d "$gun days ago" +%Y-%m-%d)
    log_dosyasi="$LOG_DIR/app-${tarih}.log"
    
    if [ -f "$log_dosyasi" ]; then
        gzip -c "$log_dosyasi" > "$ARSIV_DIR/app-${tarih}.log.gz"
        echo "Arşivlendi: $log_dosyasi"
        rm "$log_dosyasi"
    fi
done

echo "Log arşivleme tamamlandi."

Glob ve Dosya İşlemleri

for döngüsünün en sık kullandığım şekli dosya işlemleri. Bir dizindeki tüm .conf dosyalarına yedek almak mesela:

#!/bin/bash

KAYNAK="/etc/nginx/conf.d"
YEDEK="/backup/nginx/$(date +%Y%m%d_%H%M%S)"

mkdir -p "$YEDEK"

for conf_dosyasi in "$KAYNAK"/*.conf; do
    # Dosya var mı kontrol et (glob eşleşmezse)
    [ -f "$conf_dosyasi" ] || continue
    
    dosya_adi=$(basename "$conf_dosyasi")
    cp "$conf_dosyasi" "$YEDEK/$dosya_adi"
    echo "Yedeklendi: $dosya_adi"
done

echo "Toplam yedeklenen: $(ls $YEDEK | wc -l) dosya"

while Döngüsü: Koşul Sağlandığı Sürece Çalış

while döngüsü, iterasyon sayısını önceden bilmediğinizde kullanılır. Bir koşul doğru olduğu sürece döngü devam eder.

Temel Kullanım

while [ koşul ]; do
    komutlar
done

Dosyadan Satır Satır Okuma

while read kombinasyonu, benim en çok kullandığım pattern’lardan biri. CSV dosyalarını işlemek, kullanıcı listelerini okumak, yapılandırma dosyalarını parse etmek için biçilmiş kaftan:

#!/bin/bash

# kullanici_listesi.csv formatı: kullanici_adi,email,grup
KULLANICI_DOSYASI="/tmp/yeni_kullanicilar.csv"

while IFS=',' read -r kullanici email grup; do
    # Başlık satırını atla
    [[ "$kullanici" == "kullanici_adi" ]] && continue
    
    # Boş satırları atla
    [[ -z "$kullanici" ]] && continue
    
    echo "Kullanici olusturuluyor: $kullanici"
    
    # Kullanıcı zaten varsa atla
    if id "$kullanici" &>/dev/null; then
        echo "  [UYARI] $kullanici zaten mevcut, atlaniyor..."
        continue
    fi
    
    # Kullanıcıyı oluştur
    useradd -m -G "$grup" -c "$email" "$kullanici"
    
    if [ $? -eq 0 ]; then
        # Geçici şifre oluştur ve mail gönder
        gecici_sifre=$(openssl rand -base64 12)
        echo "$kullanici:$gecici_sifre" | chpasswd
        echo "Geçici şifreniz: $gecici_sifre" | mail -s "Hesap Oluşturuldu" "$email"
        echo "  [OK] $kullanici olusturuldu"
    else
        echo "  [HATA] $kullanici olusturulamadi!"
    fi
    
done < "$KULLANICI_DOSYASI"

Servis İzleme ve Yeniden Başlatma

while döngüsünün klasik kullanım alanı sürekli çalışan izleme scriptleri. Bir servisin ayakta olup olmadığını kontrol eden basit ama etkili bir script:

#!/bin/bash

SERVIS="nginx"
KONTROL_ARALIGI=30  # saniye
MAX_YENIDEN_BASLATMA=3
yeniden_baslatma_sayaci=0

echo "$(date): $SERVIS izleme basliyor..."

while true; do
    if ! systemctl is-active --quiet "$SERVIS"; then
        echo "$(date): $SERVIS DOWN - Yeniden baslatilamaya calisiliyor..."
        
        if [ $yeniden_baslatma_sayaci -lt $MAX_YENIDEN_BASLATMA ]; then
            systemctl restart "$SERVIS"
            sleep 5
            
            if systemctl is-active --quiet "$SERVIS"; then
                echo "$(date): $SERVIS basariyla yeniden baslandi"
                yeniden_baslatma_sayaci=$((yeniden_baslatma_sayaci + 1))
            else
                echo "$(date): $SERVIS yeniden baslatma BASARISIZ!"
                echo "$SERVIS yeniden baslatma basarisiz - Manuel mudahale gerekiyor!" | 
                    mail -s "KRITIK: $SERVIS CALISMIYOIR" [email protected]
            fi
        else
            echo "$(date): Maksimum yeniden baslatma sayisina ulasildi!"
            echo "MAX yeniden baslatma limiti asildi - Insan mudahalesi gerekiyor!" | 
                mail -s "KRITIK: $SERVIS - Manuel Mudahale Gerekli" [email protected]
            exit 1
        fi
    else
        # Servis çalışıyor, sayacı sıfırla
        yeniden_baslatma_sayaci=0
    fi
    
    sleep "$KONTROL_ARALIGI"
done

Disk Doluluk Uyarısı

Belirli bir eşiğe ulaşılana kadar bekleyen bir script:

#!/bin/bash

DISK_ESIGI=85  # yüzde
DIZIN="/"
UYARI_GONDERILDI=false

while true; do
    KULLANIM=$(df "$DIZIN" | awk 'NR==2 {print $5}' | tr -d '%')
    
    if [ "$KULLANIM" -gt "$DISK_ESIGI" ]; then
        if [ "$UYARI_GONDERILDI" = false ]; then
            echo "$(date): UYARI - Disk kullanimi %$KULLANIM"
            df -h "$DIZIN" | mail -s "Disk Doluluk Uyarisi: %$KULLANIM" [email protected]
            UYARI_GONDERILDI=true
        fi
    else
        UYARI_GONDERILDI=false
    fi
    
    sleep 300  # 5 dakikada bir kontrol
done

until Döngüsü: Koşul Yanlış Olduğu Sürece Çalış

until, while‘ın tam tersi mantıkla çalışır. Koşul yanlış olduğu sürece döngü devam eder, koşul doğru olduğunda durur. Yani “şu gerçekleşene kadar bekle” mantığı.

Temel Sözdizimi

until [ koşul ]; do
    komutlar
done

Servis Başlayana Kadar Bekle

Bu pattern’i deployment scriptlerinde çok kullanıyorum. Bir uygulamayı başlattıktan sonra hazır olana kadar beklemek:

#!/bin/bash

SERVIS="postgresql"
MAX_BEKLEME=60  # saniye
gecen_sure=0

echo "PostgreSQL başlamasi bekleniyor..."

systemctl start "$SERVIS"

until systemctl is-active --quiet "$SERVIS"; do
    if [ $gecen_sure -ge $MAX_BEKLEME ]; then
        echo "Hata: $SERVIS $MAX_BEKLEME saniye içinde baslamadi!"
        exit 1
    fi
    
    echo "Bekleniyor... ($gecen_sure saniye)"
    sleep 2
    gecen_sure=$((gecen_sure + 2))
done

echo "PostgreSQL hazir! ($gecen_sure saniyede basladi)"

Veritabanı Bağlantısını Bekle

CI/CD pipeline’larında container’ların birbirini beklemesi gerektiğinde bu çok işe yarıyor:

#!/bin/bash

DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-5432}"
DB_USER="${DB_USER:-postgres}"
MAX_DENEME=30
deneme=0

echo "Veritabani baglantisi bekleniyor: $DB_HOST:$DB_PORT"

until pg_isready -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" > /dev/null 2>&1; do
    deneme=$((deneme + 1))
    
    if [ $deneme -ge $MAX_DENEME ]; then
        echo "Hata: Veritabanina $MAX_DENEME denemede baglanamadi!"
        exit 1
    fi
    
    echo "Baglanti denemesi $deneme/$MAX_DENEME - 3 saniye bekleniyor..."
    sleep 3
done

echo "Veritabani baglantisi basarili!"
# Şimdi uygulamayı başlat
exec "$@"

Döngü Kontrolü: break ve continue

Döngüleri daha esnek kullanmak için break ve continue komutlarını bilmek şart.

break: Döngüyü tamamen sonlandırır. continue: Mevcut iterasyonu atlar, bir sonrakine geçer.

#!/bin/bash

# Büyük log dosyalarını bul, ilk 5 tanesini sıkıştır
SAYAC=0
LIMIT=5

for dosya in /var/log/**/*.log; do
    [ -f "$dosya" ] || continue
    
    boyut=$(du -m "$dosya" | cut -f1)
    
    # 100MB'dan küçükse atla
    if [ "$boyut" -lt 100 ]; then
        continue
    fi
    
    echo "Sikistiriliyor: $dosya ($boyut MB)"
    gzip "$dosya"
    SAYAC=$((SAYAC + 1))
    
    # 5 dosyadan sonra dur
    if [ $SAYAC -ge $LIMIT ]; then
        echo "Limit doldu ($LIMIT dosya islendi)"
        break
    fi
done

echo "Toplam islenen: $SAYAC dosya"

İç İçe Döngüler

Bazen birden fazla boyutta iterasyon gerekir. Sunucu x uygulama matrisi gibi:

#!/bin/bash

ORTAMLAR="dev staging prod"
SERVISLER="nginx postgresql redis"

for ortam in $ORTAMLAR; do
    echo ""
    echo "=== $ortam ortami kontrol ediliyor ==="
    
    for servis in $SERVISLER; do
        # Her ortamdaki her servisi kontrol et
        # Gerçekte bu bir SSH komutu olabilir
        durum=$(ssh "ops@${ortam}-server" "systemctl is-active $servis" 2>/dev/null)
        
        if [ "$durum" = "active" ]; then
            echo "  [OK] $servis"
        else
            echo "  [HATA] $servis - Durum: $durum"
        fi
    done
done

Paralel İşlem ile Döngüleri Hızlandırma

Büyük listeleri sıralı işlemek zaman alabilir. Background job’lar ile paralel çalışmak:

#!/bin/bash

SUNUCULAR=(web01 web02 web03 web04 web05 web06 web07 web08)
MAX_PARALEL=4
AKTIF_IS=0

for sunucu in "${SUNUCULAR[@]}"; do
    # Maksimum paralel iş sayısını aşmayı bekle
    while [ $AKTIF_IS -ge $MAX_PARALEL ]; do
        wait -n 2>/dev/null || wait
        AKTIF_IS=$((AKTIF_IS - 1))
    done
    
    # İşi arka planda başlat
    {
        echo "[$sunucu] Yedek aliniyor..."
        rsync -az /etc/ "backup@backup-server:/backup/$sunucu/etc/" 2>&1
        echo "[$sunucu] Yedek tamamlandi"
    } &
    
    AKTIF_IS=$((AKTIF_IS + 1))
done

# Kalan tüm işlerin bitmesini bekle
wait
echo "Tüm yedekler tamamlandi!"

Gerçek Dünya: Deployment Script

Tüm öğrendiklerimizi bir araya getiren gerçekçi bir deployment scripti:

#!/bin/bash

set -euo pipefail

UYGULAMALAR=("api-service" "worker-service" "scheduler-service")
ORTAM="${1:-staging}"
IMAGE_TAG="${2:-latest}"
BASARI_SAYISI=0
HATA_SAYISI=0

deploy_uygulama() {
    local uygulama="$1"
    local tag="$2"
    
    echo "[$uygulama] Deploy basliyor: $tag"
    
    # Image'ı çek
    if ! docker pull "sirket/$uygulama:$tag" > /dev/null 2>&1; then
        echo "[$uygulama] HATA: Image cekilemedi!"
        return 1
    fi
    
    # Container'ı yeniden başlat
    docker-compose -f "docker-compose.$ORTAM.yml" up -d "$uygulama"
    
    # Sağlık kontrolü: 60 saniye boyunca bekle
    local bekleme=0
    until docker-compose -f "docker-compose.$ORTAM.yml" ps "$uygulama" | grep -q "healthy"; do
        if [ $bekleme -ge 60 ]; then
            echo "[$uygulama] HATA: Saglik kontrolu zaman asimina ugradi!"
            return 1
        fi
        sleep 5
        bekleme=$((bekleme + 5))
    done
    
    echo "[$uygulama] Deploy basarili! ($bekleme saniyede hazir)"
    return 0
}

echo "Deploy basliyor: $ORTAM ortami, tag: $IMAGE_TAG"
echo "Uygulamalar: ${UYGULAMALAR[*]}"
echo ""

for uygulama in "${UYGULAMALAR[@]}"; do
    if deploy_uygulama "$uygulama" "$IMAGE_TAG"; then
        BASARI_SAYISI=$((BASARI_SAYISI + 1))
    else
        HATA_SAYISI=$((HATA_SAYISI + 1))
        echo "[$uygulama] Deploy basarisiz, devam ediliyor..."
    fi
done

echo ""
echo "=== Deploy Ozeti ==="
echo "Basarili: $BASARI_SAYISI/${#UYGULAMALAR[@]}"
echo "Basarisiz: $HATA_SAYISI/${#UYGULAMALAR[@]}"

if [ $HATA_SAYISI -gt 0 ]; then
    echo "Bazi uygulamalar deploy edilemedi!" | mail -s "Deploy Uyarisi: $ORTAM" [email protected]
    exit 1
fi

echo "Tum uygulamalar basariyla deploy edildi!"

Döngülerde Sık Yapılan Hatalar

Boşluk içeren dosya adları: for dosya in $(ls) yerine her zaman glob kullanın.

# Yanlis - bosluk iceren dosya adlarinda bozulur
for dosya in $(ls /tmp); do ...

# Dogru
for dosya in /tmp/*; do ...

Sonsuz döngüden çıkamamak: while true kullanıyorsanız her zaman bir çıkış koşulu ve break ekleyin. Ya da scripti timeout komutuyla çalıştırın:

timeout 3600 ./izleme_scripti.sh

IFS sorunları: while read kullanırken IFS’i ayarlamayı unutmayın, yoksa satır başı ve sonu boşlukları beklenmedik sorunlara yol açar.

# Guvenli sekilde oku
while IFS= read -r satir; do
    echo "$satir"
done < dosya.txt

Sonuç

for, while ve until döngüleri Bash scripting’in omurgasını oluşturur. Bunların hangisini ne zaman kullanacağınızı özetseyecek olursam:

  • for: Eleman sayısını önceden bildiğinizde, dosya listeleri, array iterasyonları
  • while: Koşul doğru olduğu sürece çalışması gereken durumlar, sürekli izleme, satır satır okuma
  • until: “Şu gerçekleşene kadar bekle” senaryoları, servis başlamasını bekleme, deployment sonrası sağlık kontrolü

Bu üç döngüyü break, continue ve paralel işleme teknikleriyle birleştirdiğinizde, saatlerinizi alan manuel işleri dakikalar içinde otomatik hale getirebilirsiniz. Sysadmin’in en büyük silahı tembellik değil, akıllı tembellik. Bir işi iki kez yapıyorsanız, üçüncüde script yazma vakti gelmiş demektir.

Yorum yapın