İçeriğe geç
academia.sh

Ders 10 / 24

Döngüler

Liste, sayaç ve koşul tabanlı yineleme, dosya adları üzerinde güvenli dönme, satır satır okumanın doğru biçimi ve döngü denetimi.

İçindekiler

Rapor şu an tek bir liste üretiyor. Durum kodu sınıflarının her biri için ayrı sayım istendiğinde aynı boru hattını dört kez elle yazmak gerekir. Programlama Temelleri kursunda bu, yinelemenin klasik gerekçesiydi.

Kabuktaki döngüler aynı yapıları kurar, ama listenin nereden geldiği kabuğun genişletme kurallarına bağlıdır. Bu ders döngü biçimlerini ve — kabuk programlamanın en sık yanlış yazılan parçası olan — dosyayı satır satır okuma desenini ele alır.

Liste Üzerinde Dönme

for döngüsü, kendisine verilen kelime listesinin her elemanı için gövdeyi çalıştırır.

for sinif in 2 3 4 5; do
  adet=$(cut -d' ' -f9 erisim.log | grep -c "^${sinif}")
  echo "${sinif}xx -> $adet"
done
2xx -> 18
3xx -> 3
4xx -> 6
5xx -> 3

$( ... ) gösterimi bir komutun çıktısını değere çevirir; ayrıntısı bu konunun son dersinde ele alınacak.

Listedeki “kelime” kavramı, önceki derslerde kurulan kelime bölme kavramıyla aynıdır. Liste bir değişkenden geliyorsa, tırnak kullanımı doğrudan döngünün tur sayısını belirler:

kodlar="200 404 500"
for k in $kodlar;   do printf '<%s> ' "$k"; done; echo
for k in "$kodlar"; do printf '<%s> ' "$k"; done; echo
<200> <404> <500> 
<200 404 500> 

Bu, kelime bölmenin bilinçli olarak istendiği ender durumlardan biridir. Yine de değeri tek bir dizgide tutup bölünmesine güvenmek kırılgandır; birden çok değer taşınacaksa dizi kullanmak doğru araçtır. Diziler bu konunun yedinci dersinin konusudur.

Dosya Adları Üzerinde Dönme

Bir dizindeki dosyalar üzerinde dönmenin doğru yolu, yol adı kalıbını doğrudan listeye yazmaktır:

mkdir -p kutukler; : > 'kutukler/a.log'; : > 'kutukler/b c.log'
for f in kutukler/*.log; do echo "  [$f]"; done
  [kutukler/a.log]
  [kutukler/b c.log]

Boşluk içeren ad tek parça kaldı; çünkü yol adı genişletmesinin sonucu kelime bölmeye uğramaz. Bu, ikinci derste kurulan kuralın pratik karşılığıdır.

Aynı işi for f in $(ls kutukler) biçiminde yazmak yanlıştır: ls çıktısı bir metindir ve kelime bölmeye uğrar; boşluk içeren her ad parçalanır. Kabukta dosya listesi üretmenin aracı ls değil, yol adı genişletmesidir.

Kalıbın hiçbir dosyayla eşleşmemesi ayrı bir tuzaktır:

for f in kutukler/*.yok; do echo "  [$f]"; done
  [kutukler/*.yok]

Eşleşme yoksa kabuk kalıbı harfi harfine bırakır ve döngü bir kez, var olmayan bir dosya adıyla döner. İki çözüm vardır. Taşınabilir olanı, gövdenin başına varlık sınaması koymaktır:

for f in kutukler/*.yok; do
  [ -e "$f" ] || continue
  echo "  [$f]"
done

Bash’e özgü olanı nullglob seçeneğidir; eşleşmeyen kalıp boş listeye genişler ve döngü hiç dönmez:

shopt -s nullglob
for f in kutukler/*.yok; do echo "  [$f]"; done
shopt -u nullglob

Seçenek genel bir ayardır ve etkin olduğu sürece tüm kalıpları etkiler; betiğin başında açılıp bırakılması, ilerideki satırlarda beklenmedik davranışa yol açabilir.

Sayaçlı Döngüler

Sabit sayıda tur için üç yazım vardır.

Ayraç genişletmesi derleme zamanında sabit bir liste üretir:

for i in {1..5}; do printf '%s ' "$i"; done; echo
1 2 3 4 5 

Ayraç genişletmesi değişken içeremez — {1..$n} beklendiği gibi çalışmaz, çünkü ayraç genişletmesi değişken genişletmesinden önce uygulanır. Bu, genişletme sırasının gözlemlenebilir sonuçlarından biridir.

C biçimli döngü bash eklentisidir ve değişken sınır alabilir:

for ((i = 1; i <= 5; i++)); do printf '%s ' "$i"; done; echo
1 2 3 4 5 

Dizi üreten komut, taşınabilir üçüncü yoldur: for i in $(seq 1 "$n"). Komut çağırması nedeniyle en pahalı seçenektir, ama POSIX kabuk dilinde çalışır.

Koşul Tabanlı Döngüler

while, koşul komutu 0 döndürdüğü sürece; until sıfırdan farklı döndürdüğü sürece gövdeyi yineler.

sayac=0
until [ "$sayac" -ge 3 ]; do
  echo "sayac=$sayac"
  sayac=$((sayac + 1))
done
sayac=0
sayac=1
sayac=2

İkisi birbirinin yerine kullanılabilir; until, koşulun değillemesini yazmaktan kurtarır ve okunurluğu artırdığı durumlarda yeğlenir.

Dosyayı Satır Satır Okumak

Kabuk programlamada en sık yanlış yazılan desen budur. Doğru biçim üç parçadan oluşur ve her parçanın ayrı bir gerekçesi vardır:

while IFS= read -r satir; do
  ...
done < dosya

-r seçeneği ters bölü işlemesini kapatır. Bu seçenek olmadan read, ters bölüyü bir kaçış karakteri sayar ve veriyi bozar.

IFS= ataması satırın başındaki ve sonundaki boşlukların kırpılmasını engeller.

Yönlendirme, boru değil. Boru hattının sağ ucundaki döngü alt kabukta çalışır ve atadığı değişkenler kaybolur; bu, Boru Hattı dersinde gösterilmişti.

Üç parçanın etkisi ölçülebilir. Aşağıdaki dosya üç satırdır: ilkinde iki boşluk, ikincisi ters bölüyle başlar, üçüncüsünün iki ucunda boşluk vardır.

printf 'bir  iki\n\\ters bolulu\n  bosluklu kenar  \n' > garip.txt
while read satir; do printf '<%s>\n' "$satir"; done < garip.txt
<bir  iki>
<ters bolulu>
<bosluklu kenar>
while IFS= read -r satir; do printf '<%s>\n' "$satir"; done < garip.txt
<bir  iki>
<\ters bolulu>
<  bosluklu kenar  >

İlk yazımda ters bölü yutuldu ve kenar boşlukları kırpıldı. İkincisi dosyayı olduğu gibi verdi.

Dördüncü bir ayrıntı, son satırda satır sonu bulunmamasıdır. read satır sonu görmeden dosya sonuna ulaşırsa okuduğunu değişkene yazar ama sıfırdan farklı kod döndürür; döngü koşulu bu kodu yanlış anlar ve son satır işlenmez:

printf 'birinci\nikinci' > eksik.txt
while IFS= read -r s; do echo "okundu: $s"; done < eksik.txt
okundu: birinci

Koşula bir ek yapılır: değişken boş değilse döngü bir tur daha döner.

while IFS= read -r s || [ -n "$s" ]; do echo "okundu: $s"; done < eksik.txt
okundu: birinci
okundu: ikinci

Bu kalıp, dış kaynaklı verinin okunduğu her betikte gereklidir; satır sonuyla bitmeyen dosyalar nadir değildir.

Satırı Alanlara Bölerek Okumak

read birden çok değişken alırsa satırı IFS’e göre böler ve son değişkene kalan her şeyi verir. Kütüğün alanları boşlukla ayrıldığı için doğrudan okunabilir:

head -3 erisim.log | while read -r adres _ _ _ _ yontem yol _ kod bayt; do
  printf '%-12s %-4s %-18s %s %s\n' "$adres" "${yontem#\"}" "$yol" "$kod" "$bayt"
done
10.0.0.12    GET  /index.html        200 5120
10.0.0.31    GET  /statik/stil.css   200 2048
10.0.0.12    GET  /urun/12           200 8410

_ adı bir sözleşmedir, dil öğesi değil: ilgilenilmeyen alanlar aynı değişkene atanır. ${yontem#\"} gösterimi baştaki tırnağı siler; dizgi işleme gösterimleri Aritmetik ve Komut Yerine Koyma dersinde toparlanacak.

Bu döngü, alan sayısı sabit olan veriler için yeterlidir. Alan sayısı değişken olduğunda ya da hesaplama gerektiğinde alan tabanlı bir araç daha uygundur; Metin İşleme konusunda aynı iş tek satıra inecek.

Döngü Denetimi

break içinde bulunduğu döngüyü sonlandırır, continue turu atlar. İkisi de sayı argümanı alarak dış döngülere etki edebilir.

for k in 200 301 404 500 xyz 200; do
  case "$k" in
    [0-9][0-9][0-9]) : ;;
    *) echo "gecersiz kod, atlaniyor: $k"; continue ;;
  esac
  if [ "$k" -ge 500 ]; then echo "sunucu hatasi bulundu: $k"; break; fi
  echo "islendi: $k"
done
islendi: 200
islendi: 301
islendi: 404
sunucu hatasi bulundu: 500

break 2 iki düzey dışarı çıkar:

for a in 1 2; do
  for b in x y; do
    [ "$b" = y ] && break 2
    echo "$a$b"
  done
done
1x

Sayılı break okunurluğu hızla bozar; iki düzeyden fazlasında döngüyü bir fonksiyona alıp return kullanmak daha anlaşılır olur. Fonksiyonlar sonraki dersin konusudur.

Betiğe Uygulama

rapor.sh ikinci bir bölüm kazanır: durum kodu sınıflarının dağılımı.

kutuk=erisim.log

echo "== Durum kodu siniflari =="
for sinif in 2 3 4 5; do
  adet=$(cut -d' ' -f9 "$kutuk" | grep -c "^${sinif}")
  case "$sinif" in
    2) ad="basarili"       ;;
    3) ad="yonlendirme"    ;;
    4) ad="istemci hatasi" ;;
    5) ad="sunucu hatasi"  ;;
  esac
  printf '  %sxx %-16s %3d\n' "$sinif" "$ad" "$adet"
done
== Durum kodu siniflari ==
  2xx basarili          18
  3xx yonlendirme        3
  4xx istemci hatasi     6
  5xx sunucu hatasi      3

Sayıların toplamı otuzdur; kütükteki satır sayısına eşittir. Bu tür bir iç tutarlılık sınaması, rapor betiklerinin doğruluğunu ucuz biçimde denetlemenin yoludur.

Özet

  • for listesi, kabuğun genişletme kurallarıyla oluşur; tırnaksız değişken bölünür, tırnaklı bölünmez.
  • Dosya listesi ls çıktısından değil yol adı genişletmesinden üretilir; eşleşme yoksa kalıp harfi harfine kalır ve [ -e "$f" ] || continue ya da nullglob gerekir.
  • Ayraç genişletmesi değişken alamaz, çünkü değişken genişletmesinden önce uygulanır.
  • Satır satır okumanın doğru biçimi while IFS= read -r satir; do ... done < dosya yazımıdır; boru kullanılırsa döngü alt kabuğa düşer.
  • Satır sonuyla bitmeyen dosyalarda son satır ancak || [ -n "$satir" ] ekiyle işlenir.

Sonraki Adım

Betik büyüdükçe aynı iş — hata bildirme, bölüm başlığı yazma, bir alanı sayma — birden çok yerde tekrar ediyor. Sonraki ders fonksiyonları ele alır: kabukta fonksiyon tanımının ne olduğunu, argümanların nasıl geçtiğini, return ile exit arasındaki farkı ve kabuğun kapsam modelinin Programlama Temelleri kursundakinden hangi noktada ayrıldığını.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat