Ders 11 / 24
Fonksiyonlar ve Kapsam
Fonksiyon tanımı, argüman geçişi, çıkış koduyla değer döndürmenin ayrımı, yerel değişkenler ve dinamik kapsam.
İçindekiler
Betik büyüdükçe aynı iş birden çok yerde tekrar ediyor: hata bildirme, bölüm başlığı yazma, bir alanı sayıp sıralama. Programlama Temelleri kursunda bunun karşılığı fonksiyondu ve fonksiyon, adlandırılmış parametreler alıp bir değer döndüren yapıydı.
Kabuktaki fonksiyon farklı bir şeydir. Adlandırılmış bir komuttur: konumsal parametrelerle çağrılır, çıkış kodu döndürür ve sonucunu — bir değer üretiyorsa — standart çıktısına yazar. Yani kabuk fonksiyonu, bir programın kabuk içindeki karşılığıdır.
Tanım ve Çağrı
bolum() { printf '\n== %s ==\n' "$*"; }
bolum "En cok istenen yollar"
== En cok istenen yollar ==
Gövde süslü ayraçlar arasındadır. Ayraçlar sözcük olarak tanındığı için çevrelerinde
boşluk zorunludur ve son komuttan sonra ; ya da satır sonu gerekir. Bash
function ad { ... } yazımını da tanır; POSIX olan biçim yukarıdakidir.
Fonksiyon çağrıldığı yerde tanımlı olmalıdır. Kabuk dosyayı yukarıdan aşağı okur; tanımdan önceki bir çağrı “komut bulunamadı” hatası verir:
cagir_once echo "kod=$?"
./f.sh: line 14: cagir_once: command not found kod=127
Bu yüzden fonksiyonlar betiğin başında, iş yapan kodun üstünde toplanır.
Argümanlar
Fonksiyon çağrıldığında konumsal parametreler fonksiyonunkilerle değiştirilir: $1,
$2, $#, "$@" fonksiyona verilen argümanları gösterir. Bir istisna vardır — $0
değişmez, betiğin adını göstermeye devam eder.
kim() { echo "0=$0 1=$1 sayi=$#"; }
kim a b
0=./f.sh 1=a sayi=2
Argüman aktarımında ikinci dersteki kural geçerlidir: "$@" kullanılır, $* yalnızca
ileti metni üretmek için yazılır.
Kabuk fonksiyonlarının imzası yoktur; eksik argümanla çağrılan fonksiyon hata vermez,
$1 boş kalır. Zorunlu argümanlar gövdenin başında sınanmalıdır:
sinif_adi() {
[ "$#" -eq 1 ] || { echo "sinif_adi: bir argüman bekler" >&2; return 2; }
...
}
Çıkış Kodu Döndürmek
return fonksiyonu sonlandırır ve çıkış kodunu belirler. Kod, süreç çıkış kodlarıyla
aynı sözleşmeye tabidir: 0 başarı, sıfırdan farklı başarısızlık, aralık 0–255.
gecerli_kod() {
case "$1" in
[1-5][0-9][0-9]) return 0 ;;
*) return 1 ;;
esac
}
gecerli_kod 404; echo "404 -> $?"
gecerli_kod abc; echo "abc -> $?"
404 -> 0 abc -> 1
Aralık dışı değerler 256’ya göre indirgenir; return 300 yazan bir fonksiyon 44
döndürür.
return ile exit karıştırılmamalıdır. return yalnızca fonksiyondan çıkar; exit
betiğin tamamını sonlandırır.
#!/usr/bin/env bash f() { echo "f icinde"; return 1; } g() { echo "g icinde"; exit 4; } f; echo "f sonrasi devam, kod=$?" g; echo "BU SATIR YAZILMAZ"
f icinde f sonrasi devam, kod=1 g icinde
echo $?
4
Kural: kitaplık niteliğindeki fonksiyonlar return kullanır ve kararı çağırana bırakır;
yalnızca “ölümcül hata” işlevi gören yardımcılar exit çağırır.
Değer Döndürmek
return bir değer döndürme aracı değildir; 0–255 aralığındaki bir kod, dizgi ya da büyük
sayı taşıyamaz. Kabukta değer döndürmenin yolu standart çıktıya yazmak, çağıranın da onu
komut yerine koymayla almasıdır.
sinif_adi() {
case "$1" in
2) echo "basarili" ;;
3) echo "yonlendirme" ;;
4) echo "istemci hatasi" ;;
5) echo "sunucu hatasi" ;;
*) echo "tanimsiz"; return 1 ;;
esac
}
ad=$(sinif_adi 4); echo "4 -> $ad"
4 -> istemci hatasi
Bu sözleşmenin doğrudan bir sonucu vardır: değer döndüren bir fonksiyon, standart çıktıya başka hiçbir şey yazamaz. İlerleme iletisi ya da uyarı yazması gerekiyorsa standart hataya yazar. Bu, konunun ilk dersindeki akış ayrımının pratikte neden gerekli olduğunun en açık örneğidir.
İkinci sonuç maliyettir: komut yerine koyma bir alt kabuk yaratır. Döngü içinde çok sayıda çağrı yapan betiklerde bu, ölçülebilir bir yavaşlamaya yol açar. Değeri bir genel değişkene yazmak alternatiftir; okunurluğu düşürdüğü için ancak gerektiğinde başvurulur.
Kapsam: Öntanımlı Olarak Genel
Kabukta değişkenler öntanımlı olarak geneldir. Fonksiyon içinde yapılan atama, dışarıda da görünür.
sayac=0 artir() { sayac=$((sayac + 1)); } artir; artir; echo "sayac=$sayac"
sayac=2
Bu, Programlama Temelleri kursundaki varsayılanın tersidir; orada fonksiyon gövdesindeki atama yerel bir ad yaratırdı. Kabuğun davranışı, fonksiyonun bir “komut” olarak tasarlanmasının sonucudur — komutlar da kabuk değişkenlerini görür.
local bildirimi adı fonksiyona bağlar:
x="disarida" denemel() { local x="iceride"; echo "fonksiyon icinde: $x"; } denemel; echo "fonksiyon disinda: $x"
fonksiyon icinde: iceride fonksiyon disinda: disarida
local POSIX kabuk dilinde tanımlı değildir; yaygın kabukların hemen hepsinde bulunur ama
#!/bin/sh bildiren bir betikte kullanılması taşınabilirlik güvencesini kaldırır.
Kural: fonksiyon içinde kullanılan her geçici değişken local bildirilir. Bildirilmeyen
bir ad, çağıran bağlamdaki aynı adlı değişkeni sessizce ezer; bu, uzun betiklerde en zor
bulunan hata sınıfıdır.
Dinamik Kapsam
local bildirilen bir ad, yalnızca tanımlandığı fonksiyona değil, o fonksiyonun
çağırdığı tüm fonksiyonlara görünür. Kabuğun kapsam modeli sözcüksel değil
dinamiktir.
ic() { echo "ic gorur: $gizli"; }
dis() { local gizli="dis fonksiyondan"; ic; }
dis
echo "ana kabuk gorur: [${gizli-tanimsiz}]"
ic gorur: dis fonksiyondan ana kabuk gorur: [tanimsiz]
ic fonksiyonu gizli adını hiç tanımlamadığı hâlde gördü, çünkü çağrı zinciri üzerinde
o ad etkindi. Programlama Temelleri kursunda tanıtılan sözcüksel kapsamda bu görünürlük
oluşmazdı; orada bir adın hangi tanıma bağlandığı, kodun yazıldığı yere bakılarak
belirlenirdi.
Pratik sonuç: fonksiyonlarınızın davranışı, kendilerinin görmediği bir çağrıcının yerel değişkenlerine bağlı hâle gelebilir. Bunu önlemenin yolu, fonksiyonların ihtiyaç duyduğu her değeri argüman olarak almasıdır.
local ve Çıkış Kodu
local bir komuttur ve kendi çıkış kodunu üretir. Bildirimle atamayı aynı satırda
yapmak, sağdaki komutun kodunu görünmez kılar:
basarisiz() { return 3; }
dene1() { local d; d=$(basarisiz); echo "ayri atama -> $?"; }
dene2() { local d=$(basarisiz); echo "local ile birlikte -> $?"; }
dene1; dene2
ayri atama -> 3 local ile birlikte -> 0
İkinci fonksiyonda görülen 0, local komutunun kendi kodudur. Katı kip altında bu,
başarısız bir komutun betiği durdurmamasına yol açar — Hata Yönetimi dersinde aynı tuzak
yeniden ele alınacak. Kural: bildirim ile değer atama ayrı satırlara yazılır.
Alt Kabuk Gövdeli Fonksiyonlar
Gövde süslü ayraç yerine parantezle yazılırsa fonksiyon bir alt kabukta çalışır. Dizin
değiştirme, IFS ayarlama ya da kabuk seçeneği açma gibi yan etkiler çağırana sızmaz:
gecici() ( cd /tmp && pwd )
Bedeli bir süreç yaratma maliyeti ve genel değişken atamalarının kaybolmasıdır. Yalıtım gerçekten gerekiyorsa doğru araçtır.
Betiğe Uygulama
rapor.sh, tekrar eden işleri fonksiyonlara alır:
BETIK_ADI="rapor.sh" hata() { printf '%s: hata: %s\n' "$BETIK_ADI" "$*" >&2; } uyari() { printf '%s: uyari: %s\n' "$BETIK_ADI" "$*" >&2; } oldur() { hata "$1"; exit "${2:-1}"; } bolum() { printf '\n== %s ==\n' "$*"; } sinif_adi() { case "$1" in 2) echo "basarili" ;; 3) echo "yonlendirme" ;; 4) echo "istemci hatasi" ;; 5) echo "sunucu hatasi" ;; *) echo "tanimsiz"; return 1 ;; esac } alan_sayimi() { local alan="$1" dosya="$2" cut -d' ' -f"$alan" "$dosya" | sort | uniq -c | sort -rn }
Sınamalar tek satıra iner ve hata kodları anlamlarını korur:
kutuk=erisim.log [[ "$adet" =~ ^[1-9][0-9]*$ ]] || oldur "-n icin pozitif tam sayi bekleniyor: $adet" 2 [ -f "$kutuk" ] || oldur "duz dosya degil ya da yok: $kutuk" 3 [ -r "$kutuk" ] || oldur "okuma izni yok: $kutuk" 3 [ -s "$kutuk" ] || uyari "kutuk bos: $kutuk"
./rapor.sh -n 3
== Kutuk: erisim.log == == En cok istenen 3 yol == 6 /api/veri 4 /yok.html 4 /index.html == Durum kodu siniflari == 2xx basarili 18 3xx yonlendirme 3 4xx istemci hatasi 6 5xx sunucu hatasi 3
./rapor.sh yok.log; echo "kod=$?"
rapor.sh: hata: duz dosya degil ya da yok: yok.log kod=3
oldur fonksiyonu exit çağırdığı için betik orada durur; hata ve uyari yalnızca
yazar ve kararı çağırana bırakır. Üç fonksiyonun bu biçimde ayrılması, ilerideki
derslerde katı kip eklenirken tek dokunuş noktası sağlayacak.
Özet
- Kabuk fonksiyonu adlandırılmış bir komuttur: konumsal parametrelerle çağrılır, çıkış
kodu döndürür, sonucunu standart çıktısına yazar.
$0fonksiyon içinde değişmez. returnçıkış kodu belirler ve yalnızca fonksiyondan çıkar;exitbetiği sonlandırır.- Değer döndürmenin yolu standart çıktıya yazmaktır; bu nedenle değer döndüren fonksiyon uyarılarını standart hataya yazmak zorundadır.
- Değişkenler öntanımlı olarak geneldir;
localbildirimi adı fonksiyona bağlar ve her geçici değişken için yazılmalıdır. - Kabuğun kapsamı dinamiktir:
localbir ad, çağrılan fonksiyonlara da görünür. local d=$(komut)yazımı komutun çıkış kodunu gizler; bildirim ile atama ayrılır.
Sonraki Adım
Rapordaki her sayım, kütüğü baştan sona bir kez daha okuyor. Beş sayım için beş tarama yapılıyor. Bunu tek taramaya indirmek, ara sonuçları bellekte tutmayı gerektirir. Sonraki ders dizileri ve ilişkisel dizileri ele alır; ikincisinin hangi bash sürümünden itibaren bulunduğu ve bulunmadığında ne yapılacağı da orada gösterilecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.