Bash'te for döngüsünün iki biçimi vardır. Birincisi bir kelime listesini dolaşır: for ad in kelime1 kelime2; do komutlar; done yazımında liste önce genişletilir, sonra her kelime için gövde bir kez çalışır ve ad değişkeni o anki kelimeyi taşır. İkincisi C diline benzeyen aritmetik biçimdir: for (( i = 0; i < 10; i++ )); do ...; done. Liste dolaşmak için ilkini, sayaçla ilerlemek için ikincisini kullanırsınız.

Bu yazıda iki biçimin kılavuzdaki kurallarını, {1..10} gibi dizi ifadeleriyle sayı üretmeyi, "$@" ve dizilerin doğru dolaşılmasını, dosyalar üzerinde döngü kurmayı, break ve continue kullanımını ve en sık görülen hataları örneklerle göreceksiniz. Satır satır dosya okumak için for yerine while read doğru araçtır; bu konu Bash while ve until döngüsü yazısında anlatılıyor.

Liste biçimi: for ad in kelimeler

Liste biçimi: for ad in kelimeler

Kılavuza göre for önce in sonrasındaki kelimeleri genişletir, ardından ortaya çıkan listedeki her eleman için do ile done arasındaki komutları çalıştırır. Döngü değişkeni her turda listedeki bir sonraki elemana bağlanır. Liste sabit kelimelerden, değişkenlerden, dosya desenlerinden ya da komut çıktılarından oluşabilir; hepsi döngü başlamadan bir kez genişletilir.

bash
#!/usr/bin/env bash
for meyve in elma armut "kara dut"; do
  echo "meyve: $meyve"
done
# çıktı:
#   meyve: elma
#   meyve: armut
#   meyve: kara dut

echo "döngüden sonra: $meyve"
# çıktı: döngüden sonra: kara dut

İki ayrıntıya dikkat edin. Tırnak içindeki "kara dut" tek bir eleman olarak kaldı; tırnaklar kelime bölmeyi engeller. Ayrıca döngü bittikten sonra meyve değişkeni kaybolmadı, son değeri taşımaya devam ediyor. for geçerli kabukta çalışır ve döngü değişkeni sıradan bir kabuk değişkenidir. Bir fonksiyonun içinde döngü kuruyorsanız değişkeni local ile tanımlamak dış kapsamı kirletmemek için iyi bir alışkanlıktır; bkz. local değişken ve kapsam.

do kelimesini liste ile aynı satıra yazmak için araya noktalı virgül koymanız gerekir. Kılavuz, in ve do kelimelerinin for komutunun üçüncü kelimesi olduklarında ayrılmış kelime olarak tanındığını belirtir. Tek satırlık biçim etkileşimli kabukta pratiktir: for f in a b c; do echo "$f"; done.

Sayı aralıkları: {1..10} dizi ifadesi

Belirli bir aralıkta sayı üretmenin en kısa yolu süslü parantez genişletmesindeki dizi ifadesidir. Kılavuza göre {x..y} biçimi x ile y arasındaki (ikisi dahil) her tamsayıya genişler; {x..y..adım} biçimi aralığı adım kadar artırarak üretir. x ya da y sıfırla başlıyorsa üretilen bütün terimler aynı basamak sayısına sıfırla tamamlanır. Harflerle de çalışır: {a..e}, C yerel ayarındaki sıraya göre harf üretir.

bash
#!/usr/bin/env bash
for i in {1..5}; do printf "%s " "$i"; done; echo
# çıktı: 1 2 3 4 5 
for i in {0..20..5}; do printf "%s " "$i"; done; echo
# çıktı: 0 5 10 15 20 
for i in {5..1}; do printf "%s " "$i"; done; echo
# çıktı: 5 4 3 2 1 
for i in {08..11}; do printf "%s " "$i"; done; echo
# çıktı: 08 09 10 11 
for h in {a..e..2}; do printf "%s " "$h"; done; echo
# çıktı: a c e 

{5..1} örneğinin gösterdiği gibi, adım verilmediğinde Bash yönü kendisi belirler: kılavuzdaki ifadeyle varsayılan artış duruma göre 1 ya da -1'dir. Sıfırla doldurma, log_08 gibi sıralı dosya adları üretirken işinize yarar. Süslü parantez genişletmesinin diğer kullanımları için brace expansion ve tilde yazısına bakabilirsiniz.

bash
#!/usr/bin/env bash
n=3
for i in {1..$n}; do echo "$i"; done
# çıktı: {1..3}

for (( i = 1; i <= n; i++ )); do printf "%s " "$i"; done; echo
# çıktı: 1 2 3 

Aritmetik biçim: for (( ; ; ))

İkinci biçim, C dilindeki for döngüsünün aynısıdır ve üç aritmetik ifade alır. Kılavuz işleyişi şöyle tarif eder: önce birinci ifade bir kez değerlendirilir. Sonra ikinci ifade sıfır çıkana kadar tekrar tekrar değerlendirilir; her sıfır dışı sonuçta gövde çalıştırılır ve ardından üçüncü ifade değerlendirilir. İfadeler aritmetik bağlamda olduğu için değişkenlerin başına $ koymanız gerekmez ve <, > gibi operatörler sayısal karşılaştırma yapar.

bash
#!/usr/bin/env bash
for (( i = 0; i < 10; i += 3 )); do
  printf "%s " "$i"
done
echo
# çıktı: 0 3 6 9 

for (( i = 0, j = 10; i < j; i += 2, j -= 2 )); do
  echo "i=$i j=$j"
done
# çıktı:
#   i=0 j=10
#   i=2 j=8
#   i=4 j=6

İkinci döngüde virgül operatörü ile iki değişken aynı anda başlatılıp güncellendi. Kılavuzdaki bir kural daha önemlidir: üç ifadeden herhangi biri boş bırakılırsa 1 olarak değerlendirilmiş gibi davranılır. Yani for (( ; ; )) hiç bitmeyen bir döngüdür ve ancak gövde içindeki bir break, return ya da exit ile sonlanır. Aritmetik ifadelerin operatörleri, öncelik sırası ve tamsayı sınırları Bash aritmetik işlemler yazısında ayrıntılı olarak anlatılıyor.

bash
#!/usr/bin/env bash
deneme=0
for (( ; ; )); do
  (( deneme++ ))
  if (( deneme == 3 )); then
    echo "$deneme. denemede çıkıldı"
    break
  fi
done
# çıktı: 3. denemede çıkıldı

Argümanlar üzerinde döngü: for ad ve "$@"

Argümanlar üzerinde döngü: for ad ve "$@"

in kısmını tamamen yazmazsanız for, betiğin ya da fonksiyonun konumsal parametreleri üzerinde döner. Kılavuz bunun, in "$@" yazılmış gibi davrandığını belirtir. "$@" her argümanı ayrı ve bozulmadan korur; boşluk içeren bir argüman tek eleman olarak kalır. Tırnaksız $@ ya da $* ise kelime bölmeye uğrar ve boşluklu argümanları parçalar.

bash
#!/usr/bin/env bash
set -- "rapor 1.txt" ozet.txt

for arg; do echo "[$arg]"; done
# çıktı:
#   [rapor 1.txt]
#   [ozet.txt]

for arg in $@; do echo "[$arg]"; done
# çıktı:
#   [rapor]
#   [1.txt]
#   [ozet.txt]

for arg in "$*"; do echo "[$arg]"; done
# çıktı: [rapor 1.txt ozet.txt]

Örnekte set -- ile konumsal parametreleri betiğin içinde belirledik; gerçek kullanımda bu değerler komut satırından gelir. Üç döngü üç farklı sonuç verdi: for arg ve for arg in "$@" doğru sonuçtur, tırnaksız $@ boşluklu adı ikiye böldü, "$*" ise bütün argümanları tek bir metinde birleştirdi. $@ ile $* arasındaki farkın tamamı için Bash betik argümanları ve özel değişkenler yazısına bakın.

Diziler üzerinde döngü

Bir dizinin elemanlarını dolaşmak için aynı kural geçerlidir: "${dizi[@]}" her elemanı ayrı bir kelime olarak verir. Elemanların değerine değil indeksine ihtiyacınız varsa "${!dizi[@]}" yazımı indeks listesini üretir; bu, seyrek dizilerde ya da iki diziyi paralel dolaşırken işe yarar.

bash
#!/usr/bin/env bash
sunucular=(web-01 "db 01" cache-01)

for s in "${sunucular[@]}"; do
  echo "kontrol: $s"
done
# çıktı:
#   kontrol: web-01
#   kontrol: db 01
#   kontrol: cache-01

for i in "${!sunucular[@]}"; do
  echo "$i: ${sunucular[i]}"
done
# çıktı:
#   0: web-01
#   1: db 01
#   2: cache-01

Dizilerin tanımlanması, eleman ekleme ve silme gibi işlemler Bash dizi kullanımı yazısında, anahtar ve değer çiftleri üzerinde döngü ise ilişkisel diziler yazısında anlatılıyor.

Dosyalar üzerinde döngü

Bir dizindeki dosyaları işlemenin doğru yolu, listeyi bir dosya adı deseniyle üretmektir: for f in ./*.log. Desen genişletmesi her dosya adını, içinde boşluk olsa bile, ayrı bir eleman olarak verir. Kılavuzdaki bir kurala dikkat etmeniz gerekir: desenle eşleşen hiçbir dosya yoksa ve nullglob seçeneği kapalıysa desen olduğu gibi bırakılır. Bu durumda döngü bir kez, değişkende ./*.log metniyle çalışır.

bash
#!/usr/bin/env bash
touch "./ocak raporu.log" ./subat.log

for f in ./*.log; do
  echo "işleniyor: $f"
done
# çıktı:
#   işleniyor: ./ocak raporu.log
#   işleniyor: ./subat.log

for f in ./*.csv; do
  echo "işleniyor: $f"
done
# çıktı: işleniyor: ./*.csv

shopt -s nullglob
for f in ./*.csv; do
  echo "işleniyor: $f"
done
echo "csv döngüsü bitti"
# çıktı: csv döngüsü bitti

İki yaygın çözüm vardır: betiğin başında shopt -s nullglob açmak ya da döngünün ilk satırında [[ -e $f ]] || continue yazarak var olmayan adı atlamak. İkinci yöntem, betiğin diğer bölümlerinde desen davranışını değiştirmediği için daha yerel bir çözümdür. Sonuçlar alfabetik sırayla gelir; desen seçeneklerinin tamamı Bash joker karakterler yazısında. Alt dizinleri de kapsayan aramalar için globstar seçeneği ya da harici find komutu kullanılabilir; find bu serinin kapsamı dışındadır, temel kullanımı için Linux sunucu komutları yazısına bakabilirsiniz.

break ve continue

Döngü akışını iki yerleşik komut kontrol eder. continue o anki turun geri kalanını atlayıp bir sonraki elemana geçer, break döngüden tamamen çıkar. Kılavuza göre ikisi de isteğe bağlı bir sayı alır: break 2 içten dışa doğru ikinci döngüden, yani iç içe iki döngünün ikisinden birden çıkar; continue 2 dış döngünün bir sonraki turuna geçer. Sayı 1'den küçük olamaz.

bash
#!/usr/bin/env bash
for (( i = 1; i <= 6; i++ )); do
  (( i % 2 == 0 )) && continue
  (( i > 4 )) && break
  printf "%s " "$i"
done
echo
# çıktı: 1 3 

for satir in A B C; do
  for sutun in 1 2 3; do
    if [[ $satir$sutun == B2 ]]; then
      echo "B2 bulundu, aramayı bitir"
      break 2
    fi
  done
done
# çıktı: B2 bulundu, aramayı bitir

Dönüş durumu ve boş liste

Kılavuza göre for komutunun dönüş durumu, çalışan son komutun durumudur. Liste genişledikten sonra hiç eleman kalmazsa gövde hiç çalışmaz ve dönüş durumu 0 olur. Aritmetik biçimde ise ifadelerden biri geçersizse sıfırdan farklı bir değer döner. Bu, örneğin boş bir dizi üzerinde döngü kurduğunuzda hata almayacağınız, döngünün sessizce atlanacağı anlamına gelir.

bash
#!/usr/bin/env bash
bos=()
for x in "${bos[@]}"; do
  echo "çalışmaz"
done
# $? = 0

Sık yapılan hatalar

Sık yapılan hatalar: Komut çıktısını for ile satır satır okumaya çalışmak, Döngü değişkenini tırnaksız kullanmak, Tek bir metni liste sanmak, Döngüyü boruya bağlayıp sayacı...

Komut çıktısını for ile satır satır okumaya çalışmak

for satir in $(komut) yazımı satırları değil kelimeleri dolaşır. Komut ikamesinin sonucu tırnaksız olduğu için IFS değerindeki boşluk, sekme ve yeni satır karakterlerinden bölünür, ardından her parça dosya adı genişletmesine de uğrar. Boşluk içeren satırlar parçalanır, * içeren satırlar dosya listesine dönüşebilir:

bash
#!/usr/bin/env bash
liste=$(printf "%s\n" "Ali Veli" "Ayşe Fatma")

for satir in $liste; do echo "[$satir]"; done
# çıktı:
#   [Ali]
#   [Veli]
#   [Ayşe]
#   [Fatma]

while IFS= read -r satir; do echo "[$satir]"; done <<< "$liste"
# çıktı:
#   [Ali Veli]
#   [Ayşe Fatma]

Satır okumak için while IFS= read -r kalıbını, satırları bir diziye almak için mapfile ve readarray komutunu kullanın. Aynı nedenle for f in $(ls) yazmak yerine doğrudan for f in ./* deseni kullanılmalıdır.

Döngü değişkenini tırnaksız kullanmak

Liste doğru kurulsa bile gövdede $f tırnaksız kullanılırsa boşluklu adlar yine bölünür. rm $f gibi bir komut "ocak raporu.log" yerine "ocak" ve "raporu.log" adında iki dosyayı silmeye çalışır. Gövdede de her zaman "$f" yazın.

Tek bir metni liste sanmak

for x in "$liste" yazdığınızda tırnaklar yüzünden bütün değer tek bir eleman olur ve döngü bir kez döner. Birden fazla elemanı güvenle saklamak istiyorsanız metin yerine dizi kullanın ve "${dizi[@]}" ile dolaşın.

Döngüyü boruya bağlayıp sayacı kaybetmek

Döngünün çıktısını done | sort gibi bir boru hattına verdiğinizde döngünün tamamı bir alt kabukta çalışır. Kılavuz, birden fazla komut içeren bir boru hattındaki her komutun ayrı bir alt kabukta yürütüldüğünü belirtir. Bu yüzden döngü içinde artırdığınız bir sayaç, döngü bittiğinde ana kabukta eski değerinde kalır:

bash
#!/usr/bin/env bash
adet=0
for x in c a b; do
  (( adet++ ))
  echo "$x"
done | sort
echo "adet: $adet"
# çıktı:
#   a
#   b
#   c
#   adet: 0

Çözüm, çıktıyı bir dosyaya ya da süreç ikamesine yönlendirmek veya sayacı boru hattının dışında tutmaktır. Ayrıntılar için subshell ve çalışma ortamı ve pipe, PIPESTATUS ve pipefail yazılarına bakabilirsiniz.

Aritmetik döngüde $ ve boşluk karışıklığı

for ((i=0; i<$n; i++)) çalışır ama $ gereksizdir; aritmetik bağlamda adı yazmak yeterlidir. Asıl hata, aritmetik biçimi liste biçimiyle karıştırıp for i in (( ... )) yazmaktır. Bu bir sözdizimi hatasıdır; in kelimesi yalnızca liste biçiminde kullanılır.

Küçük bir uygulama: dosyaları toplu yeniden adlandırma

Aşağıdaki betik, bir dizindeki .txt dosyalarını sıra numaralı ve sıfırla doldurulmuş adlara kopyalıyor. Liste bir desenle üretiliyor, var olmayan eşleşmeler atlanıyor, sayaç aritmetik olarak artırılıyor ve printf -v ile sıfırla doldurulmuş ad bir değişkene yazılıyor. Örnek yalnızca kendi çalışma dizini içinde dosya oluşturuyor.

bash
#!/usr/bin/env bash
mkdir -p ./girdi ./cikti
touch ./girdi/{elma,armut,"kara dut"}.txt

sayac=0
for f in ./girdi/*.txt; do
  [[ -e $f ]] || continue
  (( sayac++ ))
  printf -v yeni "./cikti/belge_%03d.txt" "$sayac"
  cp -- "$f" "$yeni"
  echo "${f##*/} => ${yeni##*/}"
done
echo "toplam: $sayac dosya"
# çıktı:
#   armut.txt => belge_001.txt
#   elma.txt => belge_002.txt
#   kara dut.txt => belge_003.txt
#   toplam: 3 dosya

${f##*/} yazımı yolun dizin kısmını atıp yalnızca dosya adını bırakır; bu ve benzeri kesme işlemleri Bash string işlemleri yazısında anlatılıyor. printf -v kullanımı için echo ve printf yazısına bakabilirsiniz. cp komutundaki --, adı tire ile başlayan bir dosyanın seçenek sanılmasını önler.

Sık Sorulan Sorular

Bash for döngüsünde 1'den 100'e kadar nasıl sayarım?

Sınırlar sabitse for i in {1..100}, sınırlardan biri değişkense for (( i = 1; i <= n; i++ )) yazın. Harici seq komutu da kullanılabilir ama bir alt süreç başlatır ve çıktısı kelime bölmeye uğrar; Bash içinde kalmak genellikle daha temizdir.

for ile while arasında nasıl seçim yaparım?

Döngüye başlamadan önce elemanlarını bildiğiniz bir liste varsa (dosyalar, argümanlar, dizi elemanları) for kullanın. Bir koşul doğru olduğu sürece devam etmesi gereken ya da girdiyi satır satır okuyan döngüler için while kullanın.

Döngünün çıktısını tek seferde dosyaya yazabilir miyim?

Evet. Yönlendirmeyi done kelimesinden sonra yazarsanız bütün döngünün çıktısı tek bir yerden gider: for f in ./*; do echo "$f"; done > ./liste.txt. Kılavuz, bir bileşik komuta bağlanan yönlendirmelerin içindeki bütün komutlara uygulandığını belirtir. Yönlendirme ayrıntıları için Bash yönlendirme yazısına bakın.

select komutu for ile ilgili mi?

Evet, kılavuz select komutunun neredeyse for ile aynı sözdizimine sahip olduğunu söyler. Fark, select komutunun listeyi numaralı bir menü olarak yazıp kullanıcıdan seçim okumasıdır; break görene kadar tekrar eder. Etkileşimli küçük menüler için kullanılı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 · while ve until döngüsü · brace expansion · diziler · betik argümanları · joker karakterler