Ders 12 / 24
Diziler ve İlişkisel Diziler
Dizinli ve ilişkisel dizilerin tanımı, toplu genişletme, seyreklik, tek geçişte sayım ve sürüm bağımlılığının denetlenmesi.
İçindekiler
Rapordaki her sayım kütüğü baştan sona bir kez daha okuyor: beş sayım için beş tarama. Küçük dosyalarda bu fark edilmez, kütük büyüdükçe belirginleşir. Tek taramaya inmek, ara sonuçları bellekte tutmayı gerektirir.
Veri Yapıları kursunda bu iş için iki yapı tanımlanmıştı: dizinle erişilen dizi ve anahtarla erişilen karma tablosu. Kabuk her ikisinin de bir biçimini sunar. Bu ders onları, sözdizim tuzaklarını ve — ilişkisel diziler için kaçınılmaz olan — sürüm bağımlılığını ele alır.
Dizinli Diziler
Dizi, parantez içinde kelime listesiyle tanımlanır. Dizinler sıfırdan başlar.
siniflar=(2 3 4 5) echo "ilk=${siniflar[0]} son=${siniflar[3]} sayi=${#siniflar[@]}"
ilk=2 son=5 sayi=4
Süslü ayraçlar zorunludur: $siniflar[0] yazımı, siniflar değişkeninin değerinin
ardına [0] metnini ekler. Ayraçsız yazımda $siniflar tek başına ilk elemanı verir
— tüm diziyi değil.
Toplu Genişletme
Diziyi bütün olarak genişletmenin iki biçimi vardır ve fark, argüman listelerindeki
"$@"–"$*" ayrımıyla aynıdır.
say() { printf 'sayi=%d\n' "$#"; printf ' <%s>\n' "$@"; }
yollar=("/api/veri" "/urun/12" "iki kelimeli yol")
say "${yollar[@]}"
say "${yollar[*]}"
say ${yollar[@]}
sayi=3 </api/veri> </urun/12> <iki kelimeli yol> sayi=1 </api/veri /urun/12 iki kelimeli yol> sayi=5 </api/veri> </urun/12> <iki> <kelimeli> <yol>
Kural değişmedi: "${dizi[@]}" yazılır. Tırnaksız biçim elemanları böler, [*]
biçimi hepsini tek metne indirger.
Diziler fonksiyonlara doğrudan geçirilemez; kabukta fonksiyon argümanları yalnızca kelime listesidir. Aktarım genişletmeyle yapılır:
yaz() { printf 'aldigim: %s\n' "$@"; }
yaz "${yollar[@]}"
aldigim: /api/veri aldigim: /urun/12 aldigim: iki kelimeli yol
Bu, dizinin kopyalanarak geçtiği anlamına gelir; fonksiyon içindeki değişiklikler çağırana dönmez.
Ekleme, Dizinler ve Seyreklik
+= işleci diziye sona ekler. ${!dizi[@]} kullanılan dizinleri verir.
yollar+=("/panel") echo "sayi=${#yollar[@]} dizinler=${!yollar[@]}"
sayi=4 dizinler=0 1 2 3
Kabuk dizileri seyrek (sparse) olabilir: bir eleman silindiğinde kalanlar kaymaz ve dizinlerde boşluk oluşur.
unset 'yollar[1]' echo "sayi=${#yollar[@]} dizinler=${!yollar[@]}" echo "son eleman=${yollar[-1]}"
sayi=3 dizinler=0 2 3 son eleman=/panel
Bu, dizi üzerinde for ((i = 0; i < ${#dizi[@]}; i++)) biçiminde dönmenin neden yanlış
olduğunu gösterir: eleman sayısı 3, ama geçerli dizinler 0, 2 ve 3’tür. Doğru yazımlar
for e in "${dizi[@]}" ya da for i in "${!dizi[@]}" biçimindedir.
unset 'yollar[1]' yazımındaki tırnak gereklidir; tırnaksız bırakılırsa [1] bir yol adı
kalıbı olarak yorumlanabilir.
Dilimleme, ${dizi[@]:baslangic:adet} biçimiyle yapılır:
tam=(a b c d e) echo "1..3: ${tam[@]:1:3}"
1..3: b c d
Komut Çıktısından Dizi Kurmak
mapfile (eşanlamlısı readarray) bir akışı satır satır diziye okur. -t seçeneği satır
sonlarını atar.
mapfile -t kodlar < <(cut -d' ' -f9 erisim.log | sort -u) echo "farkli kod sayisi=${#kodlar[@]} -> ${kodlar[*]}"
farkli kod sayisi=8 -> 200 201 301 302 304 403 404 500
dizi=( $(komut) ) biçiminde yazmak yaygın ama yanlıştır: çıktı kelime bölmeye ve yol
adı genişletmesine uğrar, boşluk içeren satırlar parçalanır. mapfile bu iki işlemi
uygulamaz. Bash 4.0 ve sonrasında bulunur.
İlişkisel Diziler
declare -A bildirimi, anahtarı dizgi olan bir dizi yaratır. Bu, kabuğun karma tablosu
karşılığıdır ve tek geçişte sayım yapmayı mümkün kılar.
declare -A sayim while read -r kod; do sayim["$kod"]=$(( ${sayim["$kod"]:-0} + 1 )) done < <(cut -d' ' -f9 erisim.log) for k in "${!sayim[@]}"; do printf '%s -> %s\n' "$k" "${sayim[$k]}"; done | sort
200 -> 17 201 -> 1 301 -> 1 302 -> 1 304 -> 1 403 -> 2 404 -> 4 500 -> 3
${sayim["$kod"]:-0} gösterimi, ilk kez görülen anahtarın sıfırdan başlamasını sağlar;
öntanımlı değer gösterimlerinin dizi elemanlarında da çalıştığını gösterir.
Anahtarların sırası tanımsızdır — karma tablosunun doğal sonucudur. Sıralı çıktı
isteniyorsa sort gerekir; yukarıdaki örnekte bu yüzden vardır.
Bir anahtarın varlığı -v testiyle sınanır:
[[ -v sayim[404] ]] && echo "404 anahtari var" [[ -v sayim[999] ]] || echo "999 anahtari yok"
404 anahtari var 999 anahtari yok
Anahtar başına toplam almak da aynı desendir. Aşağıdaki döngü, her yola gönderilen toplam bayt sayısını tek taramada hesaplar:
declare -A bayt while read -r yol b; do bayt["$yol"]=$(( ${bayt["$yol"]:-0} + b )) done < <(cut -d' ' -f7,10 erisim.log) for y in "${!bayt[@]}"; do printf '%8d %s\n' "${bayt[$y]}" "$y"; done | sort -rn | head -5
36120 /panel
25230 /urun/12
21990 /urun/45
20480 /index.html
7236 /api/veri
Sürüm Bağımlılığı
İlişkisel diziler bash’in 4.0 sürümünden itibaren bulunur. Daha eski bir bash’te
declare -A tanınmaz — ve sonuç, sessiz olduğu için tehlikelidir.
#!/usr/bin/env bash declare -A sayim sayim[404]=4 echo "404 -> ${sayim[404]}"
Bash 4 ve sonrasında:
404 -> 4
Bash 3.2’de:
iliskisel.sh: line 2: declare: -A: invalid option declare: usage: declare [-afFirtx] [-p] [name[=value] ...] 404 -> 4
Betik hata iletisi bastı ama çalışmaya devam etti ve doğru görünen bir sonuç üretti.
Nedeni şudur: bildirim başarısız olunca sayim sıradan bir dizinli dizi oldu ve 404
anahtarı sayısal bir dizin olarak yorumlandı. Anahtarlar tam sayı olduğu sürece sonuç
tesadüfen doğru çıkar.
Anahtarlar dizgi olduğunda gizlenme biter:
metin-anahtar.sh: line 3: /api/veri: syntax error: operand expected (error token is "/api/veri")
Dizinli dizi, köşeli ayraç içindeki metni aritmetik ifade sayar ve yol adını değerlendiremez.
Çözüm, gereksinimi betiğin başında sınamaktır. BASH_VERSINFO dizisi sürüm bileşenlerini
sayısal olarak tutar:
#!/usr/bin/env bash if ((BASH_VERSINFO[0] < 4)); then echo "bu betik bash 4 veya ustunu gerektirir (bulunan: $BASH_VERSION)" >&2 exit 4 fi declare -A sayim
Betik, dördüncü sürümden eski bir yorumlayıcıyla çağrıldığında hemen durur. Aşağıdaki çağrı, sürümü 3.2’de kalmış bir sistem kabuğunu açıkça seçer:
/bin/bash surum-sinama.sh echo "cikis kodu: $?"
bu betik bash 4 veya ustunu gerektirir (bulunan: 3.2.57(1)-release) cikis kodu: 4
Bu sınama, hatayı ortaya çıktığı yerde değil kaynağında bildirir. Belirli bir sürümü gerektiren her betikte bulunmalıdır.
POSIX Kabuğunda Dizi Yoktur
POSIX kabuk dili ne dizinli ne ilişkisel dizi tanımlar. Tek liste yapısı konumsal
parametrelerdir ve set -- ile doldurulabilir:
set -- 2 3 4 5 echo "sayi=$# ilk=$1 son=$4" for s in "$@"; do printf '%s ' "$s"; done; echo
sayi=4 ilk=2 son=5 2 3 4 5
Tek bir liste taşınabilir ve betiğin kendi argümanlarını ezer; ciddi bir kısıttır.
Anahtar başına sayım için POSIX çözümü kabukta veri yapısı kurmak değil, işi bunun için tasarlanmış bir araca devretmektir. Alan tabanlı işleme aracının bir satırı, bu dersteki döngünün tamamının yerini tutar ve her yerde aynı çalışır:
awk '{ sayim[$9]++ } END { for (k in sayim) print k, sayim[k] }' erisim.log | sort
200 17 201 1 301 1 302 1 304 1 403 2 404 4 500 3
Bu araç Metin İşleme konusunda ayrıntılı ele alınacak. Buradaki ders, seçimle ilgilidir: kabukta veri yapısı kurmak, verinin kabukta kalması gerektiğinde anlamlıdır; yalnızca sayım yapılacaksa dış araç hem daha hızlı hem daha taşınabilirdir.
Özet
- Dizi elemanlarına
${dizi[i]}ile erişilir; ayraçsız$diziyalnızca ilk elemanı verir. "${dizi[@]}"elemanları ayrı kelimeler olarak genişletir;[*]tek metne indirger, tırnaksız biçim böler.- Kabuk dizileri seyrek olabilir; eleman sayısıyla dizinler örtüşmeyebileceği için sayaçlı
döngü yerine
"${!dizi[@]}"kullanılır. mapfile -tbir akışı bölme ve genişletme uygulamadan diziye okur.- İlişkisel diziler bash 4.0’dan itibaren bulunur; eksik olduğunda bildirim başarısız olur
ama betik çalışmaya devam eder.
BASH_VERSINFOile sürüm sınaması zorunludur. - POSIX kabuk dilinde dizi yoktur; anahtar başına sayım için alan tabanlı araç kullanılır.
Sonraki Adım
Bu dersteki sayımlarda $(( ... )) ve $( ... ) gösterimleri gerekçelendirilmeden
kullanıldı. Sonraki ders bu iki gösterimi tanımlar: kabukta aritmetiğin neden yalnızca tam
sayı olduğunu, ondalıklı hesabın nasıl yapıldığını, komut çıktısının değere çevrilirken
hangi karakterlerin kaybolduğunu ve bu dönüşümün maliyetini.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.