İçeriğe geç
academia.sh

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 $dizi yalnı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 -t bir 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_VERSINFO ile 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat