Bash'te while döngüsü, koşul komutu başarılı (çıkış durumu 0) olduğu sürece gövdeyi tekrar çalıştırır; until ise tam tersine koşul komutu başarısız olduğu sürece çalışır ve koşul ilk kez başarılı olduğunda durur. Yazımları while komut; do gövde; done ve until komut; do gövde; done biçimindedir. En yaygın kullanımları, bir sayaçla ilerleyen döngüler, bir koşul gerçekleşene kadar bekleyen tekrarlar ve while IFS= read -r satir kalıbıyla bir dosyayı satır satır okumaktır.
Bu yazıda iki döngünün kılavuzdaki kurallarını, satır okuma kalıbının her parçasının neden orada olduğunu, son satırı kaybetme ve değişkeni boru hattında kaybetme gibi tuzakları, sonsuz döngüleri, break ve continue kullanımını ve küçük bir yapılandırma dosyası ayrıştırıcısını göreceksiniz. Liste ve sayı aralığı dolaşan döngüler için Bash for döngüsü yazısına bakabilirsiniz.
while ve until sözdizimi

Kılavuz iki döngüyü aynı kalıpla tanımlar. while, test komutlarının çıkış durumu sıfır olduğu sürece gövdeyi çalıştırır; until, test komutlarının çıkış durumu sıfırdan farklı olduğu sürece çalıştırır. İkisinin dönüş durumu da gövdede çalışan son komutun durumudur; gövde hiç çalışmadıysa 0'dır. if komutunda olduğu gibi koşul kısmı herhangi bir komut ya da komut listesi olabilir; karar listenin son komutuna göre verilir.
#!/usr/bin/env bash
i=1
while (( i <= 3 )); do
echo "while turu: $i"
(( i++ ))
done
# çıktı:
# while turu: 1
# while turu: 2
# while turu: 3
kalan=3
until (( kalan == 0 )); do
echo "geri sayım: $kalan"
(( kalan-- ))
done
# çıktı:
# geri sayım: 3
# geri sayım: 2
# geri sayım: 1until (( kalan == 0 )) ile while (( kalan != 0 )) aynı işi yapar. Hangisini seçeceğiniz okunabilirliğe bağlıdır: "şu olana kadar" diye düşündüğünüz durumlarda until daha doğal okunur, örneğin "dosya oluşana kadar bekle" ya da "bağlantı kurulana kadar dene". Koşulun kendisi bir komut olduğu için until tekrar deneme döngülerinde özellikle kullanışlıdır.
Sayaçlı döngülerde en sık hata, gövdede sayacı artırmayı unutmaktır; koşul hiç değişmediği için döngü sonsuza kadar döner. Sayacın baştan belli bir aralıkta ilerlediği durumlarda for (( ; ; )) biçimi bu hatayı zorlaştırır, çünkü artırma ifadesi döngünün başlığında durur.
Tekrar deneme kalıbı
until ile en çok yazılan döngülerden biri, başarısız olabilecek bir işlemi belirli bir sayıda yeniden denemektir. Aşağıdaki örnekte işlem bir fonksiyonla taklit ediliyor: ilk iki çağrıda başarısız, üçüncüde başarılı oluyor. Gerçek bir betikte bunun yerine bir ağ isteği ya da bir servis kontrolü olurdu ve denemeler arasına sleep eklenirdi.
#!/usr/bin/env bash
cagri=0
baglan() {
(( ++cagri >= 3 ))
}
deneme=0
until baglan; do
(( deneme++ ))
echo "deneme $deneme başarısız"
if (( deneme >= 5 )); then
echo "vazgeçildi"
break
fi
done
echo "toplam çağrı: $cagri"
# çıktı:
# deneme 1 başarısız
# deneme 2 başarısız
# toplam çağrı: 3baglan fonksiyonunun dönüş durumu, içindeki aritmetik ifadenin sonucundan gelir: (( )) sonuç sıfır değilse 0, sıfırsa 1 döndürür. Üst sınır kontrolü sayesinde işlem hiç başarılı olmasa bile döngü sonsuza kadar sürmez. Bu tür betikleri zamanlanmış olarak çalıştırmak isterseniz cron job rehberine göz atabilirsiniz.
Dosyayı satır satır okumak: while IFS= read -r
while döngüsünün en önemli kullanımı, read yerleşik komutuyla girdiyi satır satır işlemektir. read her çağrıda bir satır okur ve dosya sonuna geldiğinde sıfırdan farklı bir durum döndürür; döngü de böylece kendiliğinden biter. Girdi, done kelimesinden sonra yazılan bir yönlendirme ile verilir: done < dosya. Bu yönlendirme bütün döngüye uygulanır, böylece her read çağrısı dosyanın bir sonraki satırını alır.
Kalıptaki iki ayrıntı isteğe bağlı görünür ama ikisi de önemlidir. -r seçeneği olmadan read, ters bölü karakterini kaçış karakteri olarak yorumlar ve siler; satır sonundaki ters bölü ise bir sonraki satırla birleştirme anlamına gelir. IFS= ön eki ise yalnızca o read çağrısı için alan ayırıcıyı boşaltır; aksi halde satırın başındaki ve sonundaki boşluklar kırpılır. Farkı aynı girdiyle görebilirsiniz:
#!/usr/bin/env bash
metin=$' girintili satır \nC:\\yol\\dosya'
while read s; do echo "[$s]"; done <<< "$metin"
# çıktı:
# [girintili satır]
# [C:yoldosya]
while IFS= read -r s; do echo "[$s]"; done <<< "$metin"
# çıktı:
# [ girintili satır ]
# [C:\yol\dosya]İlk döngü girintiyi kırptı ve ters bölüleri yuttu; ikinci döngü satırları birebir korudu. Örnekte girdi olarak bir here string (<<<) kullandık, bir dosyadan okumak için done < ./dosya.txt yazarsınız. Here string ve here document yazımları için here document ve here string yazısına, read komutunun diğer seçenekleri için read ile kullanıcı girdisi yazısına bakabilirsiniz.
Son satırı kaybetmemek

Kılavuza göre read, dosya sonuna ulaştığında sıfırdan farklı bir durum döndürür. Dosyanın son satırı yeni satır karakteriyle bitmiyorsa read o satırı değişkene yine de koyar, ama dosya sonuna ulaştığı için başarısız döner. Sonuç olarak while döngüsü son satırı işlemeden biter. Son satırı yeni satır karakteriyle bitmeyen dosyalarda şu olur:
#!/usr/bin/env bash
printf 'birinci\nikinci' > ./liste.txt
while IFS= read -r s; do echo "[$s]"; done < ./liste.txt
# çıktı: [birinci]
while IFS= read -r s || [[ -n $s ]]; do echo "[$s]"; done < ./liste.txt
# çıktı:
# [birinci]
# [ikinci]|| [[ -n $s ]] eki, read başarısız olsa bile değişkende okunmuş bir şey varsa döngünün bir tur daha dönmesini sağlar. Bir sonraki çağrıda değişken boş kalacağı için döngü düzgün biçimde biter. Dosyalarınız CRLF satır sonlarıyla geliyorsa her satırın sonunda görünmez bir \r karakteri kalır; bu durumun belirtileri Bash sık hata mesajları yazısında anlatılıyor.
Koşulda birden fazla komut
Koşul kısmı tek bir komutla sınırlı değildir. && ile bağlanan iki komut, ikisi de başarılı olduğu sürece döngüyü sürdürür. Bu, "dosya bitene kadar ya da bitiş işaretine gelene kadar oku" türündeki durumlar için kullanışlıdır. Aşağıdaki döngü, girdide SON satırını gördüğü anda durur; sonraki satırlar hiç okunmaz. Noktalı virgülle ayrılmış bir liste yazarsanız yalnızca son komutun durumunun dikkate alındığını unutmayın.
#!/usr/bin/env bash
while IFS= read -r s && [[ $s != SON ]]; do
echo "işlendi: $s"
done <<< $'kayıt 1\nkayıt 2\nSON\nkayıt 3'
# çıktı:
# işlendi: kayıt 1
# işlendi: kayıt 2Satırı alanlara bölmek
read komutuna birden fazla değişken adı verirseniz satırı IFS karakterlerinden bölüp sırayla dağıtır. Kılavuza göre kelime sayısı değişken sayısından fazlaysa geri kalan her şey son değişkene gider; azsa kalan değişkenler boş değer alır. IFS değerini yalnızca o komut için değiştirmek, iki nokta ya da virgülle ayrılmış dosyaları okumanın kısa yoludur. Kullanmayacağınız alanlar için _ gibi bir yer tutucu değişken adı kullanılabilir.
#!/usr/bin/env bash
while IFS=: read -r kullanici _ uid aciklama; do
echo "$kullanici ($uid): $aciklama"
done <<'VERI'
ayse:x:1001:Ayşe Yılmaz
mehmet:x:1002:Mehmet Kaya:yönetici
VERI
# çıktı:
# ayse (1001): Ayşe Yılmaz
# mehmet (1002): Mehmet Kaya:yöneticiİkinci satırda fazladan bir alan vardı ve son değişken olan aciklama bu alanı ayırıcısıyla birlikte aldı. Bu davranış, son sütunu "satırın geri kalanı" olarak okumak istediğinizde işinize yarar. Satırları bölmek yerine bütün dosyayı bir diziye almak istiyorsanız mapfile ve readarray daha uygun bir araçtır.
Boru hattında değişken kaybı
Bir komutun çıktısını komut | while read ... biçiminde döngüye vermek çok yaygındır ama bir tuzak içerir. Kılavuza göre birden fazla komutlu bir boru hattının her parçası ayrı bir alt kabukta çalışır. Döngü de bir alt kabukta çalıştığı için içinde değiştirdiğiniz değişkenler döngü bittiğinde kaybolur:
#!/usr/bin/env bash
adet=0
printf 'a\nb\nc\n' | while IFS= read -r s; do
(( adet++ ))
done
echo "boru hattı sonrası: $adet"
# çıktı: boru hattı sonrası: 0
adet=0
while IFS= read -r s; do
(( adet++ ))
done < <(printf 'a\nb\nc\n')
echo "süreç ikamesi sonrası: $adet"
# çıktı: süreç ikamesi sonrası: 3İkinci yazımda komut çıktısı <(...) süreç ikamesiyle döngünün girdisine yönlendirildi; döngü ana kabukta kaldığı için sayaç korundu. Bir başka seçenek shopt -s lastpipe seçeneğidir: kılavuza göre bu seçenek açıkken ve iş denetimi etkin değilken boru hattının son parçası ana kabukta çalışabilir. Kılavuza göre Bash etkileşimli olmayan kabuklarda iş denetimini varsayılan olarak açmaz; bu yüzden seçenek betiklerde işe yarar, iş denetiminin açık olduğu etkileşimli kabukta ise etkisi olmaz. Ayrıntılar process substitution ve pipe ve PIPESTATUS yazılarında.
Döngü içinde girdiyi başka bir komutun yemesi
done < dosya yönlendirmesi bütün döngünün standart girdisini o dosyaya bağlar. Gövdede standart girdiden okuyan başka bir komut varsa o komut da aynı dosyadan okur ve döngünün sıradaki satırlarını "yer". Bu, döngünün beklenenden az tur dönmesine yol açan sinsi bir hatadır:
#!/usr/bin/env bash
printf '1\n2\n3\n4\n' > ./sayilar.txt
while IFS= read -r a; do
read -r b # bu da aynı dosyadan okur
echo "a=$a b=$b"
done < ./sayilar.txt
# çıktı:
# a=1 b=2
# a=3 b=4
while IFS= read -r -u 3 a; do
echo "satır: $a"
done 3< ./sayilar.txt
# çıktı:
# satır: 1
# satır: 2
# satır: 3
# satır: 4Çözüm, dosyayı standart girdi yerine başka bir dosya tanımlayıcısına (burada 3) bağlamak ve read -u 3 ile oradan okumaktır. Böylece gövdedeki komutlar standart girdiye dokunsa bile döngünün satırları etkilenmez. Döngü içinde kullanıcıdan onay almak gibi işler için de bu yöntem gerekir. Dosya tanımlayıcıları dosya tanımlayıcıları ve exec yazısında ayrıntılı olarak anlatılıyor.
Sonsuz döngüler, break ve continue
Koşul olarak her zaman 0 döndüren bir komut verirseniz döngü kendiliğinden hiç bitmez: while true ya da while :. Kılavuz : ve true komutlarının hiçbir şey yapmadan 0 döndürdüğünü belirtir. Böyle döngüler bir olay beklerken, bir menüde kullanıcı çıkış seçene kadar ya da bir kuyruktaki işler bitene kadar kullanılır ve gövdedeki break ile sonlandırılır. continue ise turun geri kalanını atlayıp koşula geri döner. İkisi de iç içe döngülerde kaçıncı döngüyü etkileyeceğini belirten bir sayı alabilir.
#!/usr/bin/env bash
kuyruk=(gorev1 "" gorev2 DUR gorev3)
i=0
while :; do
is=${kuyruk[i]}
(( i++ ))
[[ -z $is ]] && continue
[[ $is == DUR ]] && break
echo "çalıştırılıyor: $is"
done
echo "kuyruk durdu"
# çıktı:
# çalıştırılıyor: gorev1
# çalıştırılıyor: gorev2
# kuyruk durduKüçük bir uygulama: anahtar=değer yapılandırma okuyucu
Şimdi bu yazıdaki parçaları birleştirelim. Aşağıdaki betik basit bir yapılandırma dosyasını okuyup değerleri bir ilişkisel diziye koyuyor. Boş satırları ve # ile başlayan yorum satırlarını atlıyor, her satırı ilk = işaretinden bölüyor, son satırda yeni satır olmasa da onu kaybetmiyor. Değerlerdeki ek = işaretleri son değişkene gittiği için korunuyor. Örnekteki declare -A yazımı ilişkisel diziler yazısında anlatılıyor.
#!/usr/bin/env bash
printf '%s\n' '# uygulama ayarları' '' 'port=8080' 'ad=deneme sitesi' 'baglanti=a=1;b=2' > ./uygulama.conf
printf 'mod=uretim' >> ./uygulama.conf
declare -A ayar
while IFS== read -r anahtar deger || [[ -n $anahtar ]]; do
[[ -z $anahtar || $anahtar == \#* ]] && continue
ayar[$anahtar]=$deger
done < ./uygulama.conf
for k in port ad baglanti mod; do
echo "$k -> ${ayar[$k]}"
done
# çıktı:
# port -> 8080
# ad -> deneme sitesi
# baglanti -> a=1;b=2
# mod -> uretimIFS== ilk bakışta garip görünebilir: ilk = atama işaretidir, ikincisi ise IFS değişkenine verilen değerdir. Desen içindeki \#* yazımında ters bölü, # karakterinin yorum başlangıcı sanılmaması için kullanıldı. Bu okuyucu, değerleri source ile doğrudan çalıştırmaktan daha güvenlidir, çünkü dosyadaki hiçbir satır komut olarak yürütülmez. source komutunun ne zaman uygun olduğunu source ve nokta komutu yazısında bulabilirsiniz.
Sık yapılan hatalar

- do kelimesini unutmak:
while koşul; gövde; doneyazımı "syntax error near unexpected token" hatası verir; koşuldan sonra mutlakadogelmelidir. - read komutunu -r olmadan kullanmak: Ters bölü içeren satırlar (Windows yolları, kaçışlı metinler) bozulur.
- IFS= yazmamak: Girintili satırların başındaki ve sonundaki boşluklar kaybolur.
- cat dosya | while ... kullanmak: Döngü alt kabukta çalışır, değişkenler kaybolur;
done < dosyayazın. - Sayacı artırmayı unutmak: Koşul hiç değişmez ve döngü sonsuza kadar döner.
- Gövdedeki komutların girdiyi yemesi: Gövdede standart girdiden okuyan bir komut varsa döngüyü başka bir dosya tanımlayıcısından besleyin.
Sık Sorulan Sorular
while ile until arasındaki fark nedir?
while koşul komutu başarılı olduğu sürece, until koşul komutu başarısız olduğu sürece döner. until komut yazmak while ! komut yazmakla aynı sonucu verir; seçim, hangisinin daha okunaklı olduğuna göre yapılır.
Bash ile bir dosyanın her satırını nasıl işlerim?
while IFS= read -r satir || [[ -n $satir ]]; do ...; done < dosya kalıbını kullanın. Bu kalıp girintiyi ve ters bölüleri korur, son satırı da kaybetmez. Satır satır okumak için for satir in $(cat dosya) kullanmayın; bu yazım satırları değil kelimeleri dolaşır.
Döngüden sonra içerde değiştirdiğim değişken neden boş?
Büyük olasılıkla döngü bir boru hattının parçası ve bu yüzden bir alt kabukta çalışıyor. Girdiyi done < dosya ya da done < <(komut) ile verin veya betiklerde shopt -s lastpipe kullanın.
Bash döngüsünü belirli aralıklarla nasıl çalıştırırım?
Gövdenin sonuna harici sleep komutunu ekleyerek while true; do işler; sleep 60; done yazabilirsiniz. Ancak sürekli açık kalan bir betik yerine görevi cron ile zamanlamak ya da betiği bir systemd servisi olarak çalıştırmak çoğu zaman daha sağlamdır.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Bash öğrenme rehberi · for döngüsü · read komutu · mapfile · process substitution · dosya tanımlayıcıları