Sunucu Yönetimi & Linux

    Bash'te Döngüler ve Koşullar

    Bash'te if koşulları, for/while döngüleri ve case yapısıyla mantık kurmayı öğreten pratik rehber.

    12 dk okuma Güncellendi: 10 Temmuz 2026

    Bir sunucuyu düzenli yönetmeye başladığınızda tek tek komut yazmanın sınırına hızla çarparsınız. "Şu servis çalışıyor mu, çalışmıyorsa yeniden başlat", "bu klasördeki tüm eski yedekleri sil", "disk yüzde 90'ı geçtiyse bana haber ver" gibi işler tek satırlık komutlarla değil, karar veren ve tekrar eden mantıkla çözülür. İşte tam bu noktada Bash'in koşul ve döngü yapıları devreye girer. Bunlar betiklerinizi düz bir komut listesinden, duruma göre farklı yollar izleyen gerçek bir otomasyon aracına dönüştürür.

    Bu rehberde yıllardır üretim sunucularında betik yazan bir sistem yöneticisinin gözünden if/elif/else, test ifadeleri, karşılaştırma operatörleri, for, while, until döngüleri ve case yapısını örneklerle ele alacağız. Amaç sözdizimini ezberletmek değil; hangi yapıyı ne zaman seçeceğinizi, hangi tuzaklara düşmeyeceğinizi ve gerçek senaryolarda bu araçları nasıl birleştireceğinizi göstermek. Temel komutlara henüz aşina değilseniz önce Linux temel komutları rehberimize göz atmanız, buradaki örnekleri çok daha rahat takip etmenizi sağlar.

    Koşullu Mantığın Temeli if elif else#

    Her karar bir soruyla başlar ve Bash'te bu sorunun karşılığı if bloğudur. Yapının mantığı basittir: bir komut çalıştırılır, o komutun çıkış kodu (exit status) sıfırsa "başarılı/doğru" kabul edilir ve then altındaki satırlar çalışır. Sıfırdan farklı bir çıkış kodu "başarısız/yanlış" anlamına gelir. Bu, çoğu programlama dilinden farklı bir mantıktır; alışması birkaç örnek alır ama oturunca çok güçlüdür.

    #!/usr/bin/env bash
    
    if systemctl is-active --quiet nginx; then
        echo "Nginx çalışıyor."
    else
        echo "Nginx durmuş, yeniden başlatılıyor..."
        systemctl restart nginx
    fi
    

    Burada systemctl is-active komutunun kendisi koşuldur; ayrı bir karşılaştırmaya gerek yoktur çünkü komut zaten anlamlı bir çıkış kodu döndürür. Birden fazla ihtimali sıraya dizmek istediğinizde elif (else if) zincirini kullanırsınız. Aşağıdaki örnek disk doluluğunu üç kademede değerlendirir:

    kullanim=$(df / | awk 'NR==2 {print $5}' | tr -d '%')
    
    if [[ "$kullanim" -ge 90 ]]; then
        echo "KRİTİK: Kök disk %$kullanim dolu!"
    elif [[ "$kullanim" -ge 75 ]]; then
        echo "UYARI: Disk %$kullanim seviyesinde."
    else
        echo "Disk durumu normal (%$kullanim)."
    fi
    

    elif bloklarını istediğiniz kadar zincirleyebilirsiniz, ancak dörtten fazla dal gerekiyorsa çoğu zaman ilerideki case yapısı daha okunaklı olur. Bir de sık yapılan hataya dikkat çekelim: then ile fi arasını kapatmayı unutmak yaygın bir sözdizimi hatasıdır. if ile açılan her blok mutlaka fi ile kapanır.

    Test İfadeleri ve Köşeli Parantezler#

    Yukarıdaki örneklerde [[ ... ]] kullandık. Bu, Bash'in gömülü test yapısıdır ve modern betiklerde tercih edilen yöntemdir. Klasik [ ... ] (tek köşeli parantez, aslında test komutunun kısayolu) da çalışır, fakat [[ ]] daha güvenli ve daha yeteneklidir. Aradaki farkı bilmek, açıklamasını saatlerce arayacağınız hatalardan sizi korur.

    dosya="/etc/nginx/nginx.conf"
    
    if [[ -f "$dosya" ]]; then
        echo "Yapılandırma dosyası mevcut."
    fi
    
    if [[ -d "/var/www" && -w "/var/www" ]]; then
        echo "/var/www dizini var ve yazılabilir."
    fi
    

    [[ ]] yapısının en önemli avantajı, içindeki değişkenlerin kelime bölünmesine (word splitting) ve glob genişlemesine uğramamasıdır. Yani boş ya da boşluk içeren bir değişken, [ ] içinde beklenmedik biçimde patlarken [[ ]] içinde güvenle çalışır. Yine de değişkenleri çift tırnak içine almayı alışkanlık hâline getirin; bu, hem tutarlılık hem de olası düzenli ifade karışıklıklarını önlemek için iyi bir disiplindir.

    Aşağıdaki tablo en sık kullanılan dosya ve string test operatörlerini özetliyor:

    OperatörDoğru olduğu durum
    -e yolYol var (dosya ya da dizin)
    -f yolNormal bir dosya var
    -d yolDizin var
    -r / -w / -xOkuma / yazma / çalıştırma izni var
    -s yolDosya var ve boyutu sıfırdan büyük
    -z metinString boş (uzunluk sıfır)
    -n metinString boş değil

    Bu operatörleri && (ve) ile || (veya) kullanarak birleştirebilirsiniz. Örneğin bir yedekleme betiğinde "dizin var ve içi boş değilse" kontrolü, gereksiz işlem yapmanızı önler ve otomasyonu güvenli kılar.

    Sayısal ve String Karşılaştırma Operatörleri#

    Bash'in en çok tökezleten yanı, sayı ile metni farklı operatörlerle karşılaştırmasıdır. Sayılar için -eq, -ne, -lt, -le, -gt, -ge kullanılır; metinler için = (veya ==), !=, <, > geçerlidir. 5 > 3 yazıp da sonucu ters bulan herkesin nedeni budur: > metinsel sıralamaya göre çalışır, sayısal büyüklüğe göre değil.

    KarşılaştırmaSayısalString
    Eşit-eq= veya ==
    Eşit değil-ne!=
    Küçük-lt<
    Küçük veya eşit-le(doğrudan yok)
    Büyük-gt>
    Büyük veya eşit-ge(doğrudan yok)

    Pratik bir örnekle farkı görelim:

    a=10
    b=9
    
    # Sayısal karşılaştırma — doğru sonuç
    if [[ "$a" -gt "$b" ]]; then
        echo "$a sayısı $b sayısından büyük."   # Bu çalışır
    fi
    
    # Metinsel karşılaştırma — yanıltıcı
    if [[ "$a" > "$b" ]]; then
        echo "Bu satır ASLA çalışmaz çünkü '10' metni '9' metninden küçüktür."
    fi
    

    Salt sayısal işlemler için (( ... )) aritmetik bağlamı da çok kullanışlıdır. İçinde $ işaretine bile gerek kalmadan doğal matematiksel operatörlerle yazabilirsiniz: if (( a > b )). String karşılaştırmalarında ise desen eşleştirme yapabilmeniz büyük kolaylıktır; [[ "$isim" == web-* ]] ifadesi "web-" ile başlayan her değeri yakalar. Bu desen yeteneği, aşağıda göreceğimiz döngülerle birleştiğinde çok işinize yarayacak.

    for Döngüsü Liste ve C Tarzı#

    for döngüsü, elinizde belli bir öğe kümesi olduğunda ilk tercihinizdir. Bash'te iki temel biçimi vardır. İlki ve en yaygını, bir listenin üzerinde dönen klasik biçimdir:

    for servis in nginx mysql redis; do
        if systemctl is-active --quiet "$servis"; then
            echo "$servis: aktif"
        else
            echo "$servis: KAPALI"
        fi
    done
    

    Liste sabit olmak zorunda değildir; bir komutun çıktısını, glob desenini ya da bir dizini de üzerinde dönebilirsiniz. Örneğin belirli bir dizindeki tüm .log dosyalarını sıkıştırmak isterseniz:

    for log in /var/log/uygulama/*.log; do
        [[ -e "$log" ]] || continue   # Eşleşme yoksa deseni atla
        gzip "$log"
        echo "Sıkıştırıldı: $log"
    done
    

    Buradaki [[ -e "$log" ]] || continue satırı önemli bir inceliktir: eğer dizinde hiç .log dosyası yoksa, Bash deseni olduğu gibi bir string olarak döngüye verir ve gzip var olmayan bir dosyada hata üretir. Bu kontrol o durumu zarifçe atlar. İkinci biçim ise C dilini andıran sayaç tabanlı döngüdür; belli sayıda tekrar ya da indeks bazlı işlemler için idealdir:

    for (( i=1; i<=5; i++ )); do
        echo "Deneme $i/5..."
        if curl -fsS "https://clou.tr" >/dev/null; then
            echo "Bağlantı başarılı."
            break
        fi
        sleep 3
    done
    

    break döngüyü tamamen sonlandırır, continue ise mevcut turu atlayıp bir sonrakine geçer. Bu iki anahtar kelime, döngü içi mantığı temiz tutmanın anahtarıdır. Toplu dosya işlemleri, kullanıcı hesaplarını gezme veya sunucu listesini tarama gibi işler için for neredeyse her zaman en okunaklı çözümdür.

    while ve until Döngüleri#

    Öğe sayısını önceden bilmiyorsanız, bir koşul doğru olduğu sürece dönmek istiyorsanız while döngüsü doğru araçtır. En klasik kullanımı bir dosyayı satır satır okumaktır ve burada da bir tuzak vardır: satırları doğru okumak için IFS ve read -r kombinasyonunu kullanmalısınız.

    while IFS= read -r satir; do
        # Boş satırları ve # ile başlayan yorumları atla
        [[ -z "$satir" || "$satir" == \#* ]] && continue
        echo "İşleniyor: $satir"
    done < "/etc/sunucu-listesi.txt"
    

    IFS= baştaki ve sondaki boşlukların korunmasını, -r ise ters bölü işaretlerinin (\) yanlış yorumlanmamasını sağlar. Bu ikili, dosya okuyan her betikte olması gereken standart bir kalıptır. while ayrıca bir koşul sağlanana kadar bekleyen "yoklama" döngülerinde de sık kullanılır; örneğin bir servisin ayağa kalkmasını beklemek için.

    until döngüsü ise while'ın tersidir: koşul yanlış olduğu sürece döner, doğru olunca durur. "Şu şey gerçekleşene kadar bekle" cümlesini doğrudan koda dökmek istediğinizde okunabilirliği artırır.

    echo "Veritabanının hazır olması bekleniyor..."
    until mysqladmin ping --silent; do
        echo "Henüz hazır değil, 2 saniye bekleniyor..."
        sleep 2
    done
    echo "Veritabanı ayakta."
    

    Aynı işi while ! mysqladmin ping ile de yazabilirdiniz; until yalnızca niyetinizi daha açık ifade eder. Hangisini seçeceğiniz büyük ölçüde okunabilirlik tercihidir. Bu tür bekleme döngülerini yazarken sonsuz döngüye düşmemek için mutlaka bir üst sınır ya da zaman aşımı koymayı düşünün; aksi hâlde beklenen olay hiç gerçekleşmezse betik sonsuza kadar takılır. Bu betikleri belirli aralıklarla otomatik çalıştırmak isterseniz Linux cron görevleri rehberimizdeki zamanlama yöntemlerini kullanabilirsiniz.

    case Yapısıyla Çoklu Dallanma#

    Bir değişkeni birçok olası değere karşı sınamak gerektiğinde, uzun if/elif zincirleri hem okunması zorlaşır hem de hata yapmaya açıktır. case yapısı tam bu senaryo için tasarlanmıştır ve desen eşleştirmeyi desteklediği için oldukça esnektir. Servis yönetim betiklerinde gördüğümüz o klasik start/stop/restart menüsü, aslında birer case bloğudur.

    case "$1" in
        start)
            echo "Servis başlatılıyor..."
            systemctl start uygulamam
            ;;
        stop)
            echo "Servis durduruluyor..."
            systemctl stop uygulamam
            ;;
        restart|reload)
            echo "Servis yeniden başlatılıyor..."
            systemctl restart uygulamam
            ;;
        status)
            systemctl status uygulamam
            ;;
        *)
            echo "Kullanım: $0 {start|stop|restart|status}"
            exit 1
            ;;
    esac
    

    Her dal ;; ile biter, birden fazla deseni | ile aynı bloğa bağlayabilirsiniz (restart|reload gibi) ve *) sonda "hiçbiri eşleşmezse" durumunu yakalar; bu son dalı koymak, geçersiz girdileri düzgün karşılamak için önemlidir. case desenleri glob mantığıyla çalışır, yani *.txt, web-?? veya [0-9]* gibi kalıplar kurabilirsiniz. Örneğin bir dosya uzantısına göre farklı işlem yapmak isterseniz:

    case "$dosya" in
        *.tar.gz|*.tgz) tar -xzf "$dosya" ;;
        *.zip)          unzip "$dosya" ;;
        *.gz)           gunzip "$dosya" ;;
        *)              echo "Bilinmeyen format: $dosya" ;;
    esac
    

    Bu okunabilirlik farkı özellikle uzun betiklerde bakım maliyetini ciddi biçimde düşürür. Bir menü, bir komut ayrıştırıcı ya da uzantı/durum bazlı dallanma yazıyorsanız case neredeyse her zaman if zincirinden daha temiz sonuç verir.

    Gerçek Senaryolar Dosya Kontrolü ve Toplu İşlem#

    Teoriyi bir araya getirip gerçek bir işe bakalım. Aşağıdaki betik, bir yedek dizinindeki 7 günden eski arşivleri temizler, ama önce dizinin varlığını ve boş olup olmadığını kontrol eder. Bu, üretim ortamında güvenle çalışacak türden savunmacı bir yazım örneğidir.

    #!/usr/bin/env bash
    set -euo pipefail
    
    yedek_dizini="/yedekler"
    gun_esik=7
    
    # Dizin gerçekten var mı?
    if [[ ! -d "$yedek_dizini" ]]; then
        echo "HATA: $yedek_dizini bulunamadı." >&2
        exit 1
    fi
    
    silinen=0
    for dosya in "$yedek_dizini"/*.tar.gz; do
        [[ -e "$dosya" ]] || continue
    
        # Dosya kaç günlük?
        yas=$(( ( $(date +%s) - $(stat -c %Y "$dosya") ) / 86400 ))
    
        if (( yas > gun_esik )); then
            rm -f "$dosya"
            echo "Silindi ($yas günlük): $dosya"
            (( silinen++ ))
        fi
    done
    
    echo "Toplam $silinen eski yedek temizlendi."
    

    Betiğin başındaki set -euo pipefail satırı, ciddi betik yazanların vazgeçilmezidir: -e bir komut hata verirse betiği durdurur, -u tanımsız değişken kullanımını yakalar, pipefail ise boru hattındaki (pipe) herhangi bir hatanın fark edilmesini sağlar. Bu üçlü, sessizce yanlış çalışan ve fark edilmeden veri silen betiklerin önüne geçer.

    İkinci bir yaygın senaryo, bir sunucu listesine toplu ping atıp erişilemeyenleri raporlamaktır. Burada while read ile if birlikte çalışır:

    while IFS= read -r sunucu; do
        [[ -z "$sunucu" ]] && continue
        if ping -c 1 -W 2 "$sunucu" &>/dev/null; then
            echo "AYAKTA  -> $sunucu"
        else
            echo "ERİŞİLEMİYOR -> $sunucu"
        fi
    done < sunucular.txt
    

    Bu iki örnek, aslında bir bakım penceresinde manuel yapacağınız işlerin dakikalar yerine saniyeler sürmesini sağlar. Kendi altyapınızda buna benzer otomasyonları güvenle çalıştırabileceğiniz kök erişimli bir ortam için VDS sunucu çözümlerimize göz atabilir, işletim ve güncelleme yükünü bize devretmek isterseniz sunucu yönetimi hizmetimizden faydalanabilirsiniz. Kritik verilerinizi bu betikler yanlışlıkla silmeden önce düzenli yedekleme almanızı da özellikle öneririz.

    Sıkça Sorulan Sorular#

    Bash'te [ ] ile [[ ]] arasındaki fark nedir?#

    Tek köşeli parantez [ ] aslında test komutunun bir kısayoludur ve POSIX uyumludur, ancak içindeki değişkenler kelime bölünmesine ve glob genişlemesine uğradığı için boş veya boşluklu değerlerde beklenmedik hatalar verir. Çift köşeli parantez [[ ]] ise Bash'e özgü gelişmiş bir yapıdır; değişkenleri daha güvenli işler, desen eşleştirme ve &&, || gibi mantıksal operatörleri doğrudan destekler. Betikleriniz Bash için yazılıyorsa neredeyse her durumda [[ ]] tercih edilmelidir.

    Sayıları neden -gt ve -lt ile karşılaştırmam gerekiyor?#

    Bash'te > ve < operatörleri metinsel (sözlük sırasına göre) karşılaştırma yapar, sayısal büyüklüğü dikkate almaz. Bu yüzden [[ "10" > "9" ]] ifadesi yanlış sonuç verir çünkü metin olarak "1" karakteri "9" karakterinden önce gelir. Sayıları doğru kıyaslamak için -gt, -lt, -ge, -le, -eq, -ne operatörlerini ya da (( ... )) aritmetik bağlamını kullanmalısınız. Aritmetik bağlam içinde if (( a > b )) yazımı hem daha okunaklıdır hem de doğru çalışır.

    for döngüsü mü while döngüsü mü kullanmalıyım?#

    Elinizde önceden belli bir öğe kümesi varsa, örneğin bir dosya listesi, sabit bir dizi ya da belirli sayıda tekrar gerekiyorsa for döngüsü daha uygun ve okunaklıdır. Kaç kez döneceğinizi bilmiyorsanız ve bir koşul sağlandığı sürece devam etmek istiyorsanız while daha doğru seçimdir; özellikle dosyayı satır satır okumak veya bir olayı beklemek için idealdir. Kısacası "kaç tane" belliyse for, "ne zamana kadar" belirsizse while diye düşünebilirsiniz.

    Betiğim bir dosyayı satır satır okurken satırlar bozuluyor, neden?#

    Bunun en yaygın nedeni read komutunu yalın kullanmaktır. Baştaki ve sondaki boşlukların korunması için IFS= ayarını, ters bölü işaretlerinin ham okunması için de -r bayrağını eklemelisiniz. Doğru kalıp while IFS= read -r satir; do ... done < dosya.txt biçimindedir. Bu iki küçük ekleme, boşluk ve özel karakter içeren satırların olduğu gibi okunmasını sağlar ve dosya işleme betiklerindeki en sık yaşanan sorunu ortadan kaldırır.

    case yapısını ne zaman if yerine tercih etmeliyim?#

    Tek bir değişkeni çok sayıda olası değere veya desene karşı sınadığınızda case çok daha temiz ve okunabilir bir çözüm sunar. Üç dörtten fazla elif dalınız oluşmaya başladıysa, ya da servis kontrol betiklerindeki gibi start/stop/restart türü menülere ihtiyacınız varsa case neredeyse her zaman daha iyidir. Ayrıca desen eşleştirmeyi desteklediği için dosya uzantısına ya da isim kalıbına göre dallanmak gibi işlerde if zincirinden çok daha kısa ve bakımı kolay kod üretir.

    set -euo pipefail satırını neden her betiğe koymalıyım?#

    Bu satır betiğinizi çok daha güvenli hâle getirir çünkü sessiz hataların önüne geçer. -e herhangi bir komut başarısız olduğunda betiği anında durdurur, böylece hatalı bir adımdan sonra yanlış işlemler zincirlenmez. -u tanımsız bir değişkeni kullanmaya çalıştığınızda uyarır ki bu, yazım hatalarını yakalamanın en kolay yoludur. pipefail ise bir boru hattındaki herhangi bir komutun hatasını görünür kılar. Bu üçlü, özellikle veri silen veya taşıyan betiklerde felaketleri önlemek için standart bir başlangıç kabul edilir.

    Kapanış#

    Koşullar ve döngüler, Bash betiklerini basit komut listelerinden gerçek karar verebilen otomasyon araçlarına dönüştüren temel yapı taşlarıdır. if/elif/else ile mantık kurmayı, [[ ]] içinde güvenli testler yazmayı, sayısal ve metinsel karşılaştırmaları ayırt etmeyi, for, while ve until ile tekrarları yönetmeyi ve case ile çoklu dallanmayı bir araya getirdiğinizde, günlük sunucu işlerinin büyük kısmını otomatikleştirebilirsiniz. En önemlisi, savunmacı yazım alışkanlıkları (değişkenleri tırnaklamak, varlık kontrolü yapmak, set -euo pipefail kullanmak) ile bu betikleri üretim ortamında güvenle çalıştırabilir hâle gelirsiniz.

    Bu bilgileri uygulayabileceğiniz kök erişimli, esnek bir ortam arıyorsanız Clou.TR'nin sanal sunucu ve VDS çözümleri betiklerinizi çalıştırmak için ideal bir başlangıçtır. Kurulum, güncelleme ve izleme yükünü uzman ekibimize bırakmak isterseniz sunucu yönetimi hizmetimiz devreye girer; verilerinizi güvenceye almak için de düzenli yedekleme paketlerimizi değerlendirebilirsiniz. Otomasyonunuzu kurun, gerisini bize bırakın.

    BashScriptLinux

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.