Bash ilişkisel dizisi (associative array), değerlere sayı yerine metin anahtarlarla ulaştığınız bir anahtar/değer tablosudur. Önce declare -A renk ile bildirir, sonra renk[kirmizi]=ff0000 biçiminde atama yaparsınız. ${renk[kirmizi]} tek bir değeri, "${!renk[@]}" tüm anahtarları, "${renk[@]}" tüm değerleri verir. Bir anahtarın var olup olmadığını [[ -v 'renk[kirmizi]' ]] ile kontrol edersiniz.

Bu yazıda ilişkisel dizilerin tanımlanmasını, anahtarlar üzerinde dolaşmayı, anahtar kontrolünü, silmeyi ve kelime sayma gibi pratik kullanımları çalışan örneklerle anlatıyoruz. En sık düşülen tuzak olan "declare -A yazmayı unutmak" ayrı bir başlıkta. Sayısal indeksli diziler için Bash dizi yazısına, serinin genel haritası için Bash öğrenme rehberine bakabilirsiniz.

İlişkisel dizi nedir, indeksli diziden farkı ne?

İlişkisel dizi nedir, indeksli diziden farkı ne?

İndeksli dizide anahtar her zaman sıfır veya pozitif bir tamsayıdır ve aritmetik ifade olarak değerlendirilir. İlişkisel dizide anahtar herhangi bir metin olabilir: kullanici, koyu mavi, /etc/hosts gibi. Kılavuz, ilişkisel dizi anahtarlarına tilde, parametre, aritmetik genişletme, komut ikamesi ve tırnak kaldırma uygulandığını, ancak boş metnin anahtar olarak kullanılamayacağını belirtir.

İkinci büyük fark: ilişkisel dizi yalnızca declare -A ile oluşturulabilir. İndeksli dizi herhangi bir ad[indis]=değer atamasıyla kendiliğinden doğar, ama Bash bir değişkene kendi başına ilişkisel dizi niteliği vermez. Bir fonksiyon içinde local -A veya declare -A kullandığınızda dizi o fonksiyona yerel olur; genel kapsamda oluşturmak için declare -gA yazılır. Bildirim seçeneklerinin tamamı declare ve readonly yazısında ele alınıyor.

En sık hata: declare -A yazmayı unutmak

declare -A olmadan metin anahtarlı bir atama yaparsanız Bash hata vermez; diziyi indeksli kabul eder ve köşeli parantezin içini aritmetik ifade olarak değerlendirir. Aritmetikte tanımsız bir değişken adı 0 sayıldığı için bütün "anahtarlar" aynı 0 numaralı indise yazılır ve her atama bir öncekinin üzerine biner.

bash
# declare -A YOK: kirmizi ve mavi aritmetikte 0 sayılır
r[kirmizi]=ff0000
r[mavi]=0000ff
declare -p r
# çıktı: declare -a r=([0]="0000ff")

# doğrusu
declare -A renk
renk[kirmizi]=ff0000
renk[mavi]=0000ff
echo "${#renk[@]} anahtar, kirmizi=${renk[kirmizi]}"
# çıktı: 2 anahtar, kirmizi=ff0000

declare -p çıktısındaki -a harfi değişkenin indeksli dizi olduğunu, -A ise ilişkisel dizi olduğunu gösterir. Betiğiniz tuhaf davranıyorsa ilk bakılacak yer burasıdır. Bir değişken bir kez indeksli dizi olarak oluştuktan sonra onu sonradan declare -A ile ilişkisele çeviremezsiniz; Bash bunu cannot convert indexed to associative array hatasıyla reddeder.Önce unset ile kaldırıp yeniden bildirmeniz gerekir.

Tanımlama ve değer atama biçimleri

Bildirimle birlikte bileşik atama yapabilirsiniz: declare -A baskent=([tr]=Ankara [de]=Berlin). Kılavuz ikinci bir biçimi de tanımlar: köşeli parantez kullanmadan anahtar ve değeri sırayla yazmak. declare -A b=(tr Ankara de Berlin) yazımı bir öncekiyle aynı sonucu verir. Bu biçimde listedeki son anahtarın değeri eksikse boş metin kabul edilir.

bash
declare -A ulke=(tr Türkiye de Almanya fr)
declare -p ulke
# çıktı: declare -A ulke=([de]="Almanya" [fr]="" [tr]="Türkiye" )

# iki biçim aynı listede karıştırılamaz: ilk kelime türü belirler
declare -A karisik=(x 1 [y]=2)
declare -p karisik
# çıktı: declare -A karisik=(["[y]=2"]="" [x]="1" )

# += yeni çiftler ekler, var olan anahtarın değerini değiştirir
declare -A stok=([kalem]=5)
stok+=([silgi]=3 [kalem]=9)
echo "kalem=${stok[kalem]} silgi=${stok[silgi]}"
# çıktı: kalem=9 silgi=3

Karışık örnekte ilk kelime x köşeli parantezsiz olduğu için Bash listenin tamamını anahtar/değer sırası olarak okur; [y]=2 ifadesi bir atama olarak değil, düz bir anahtar metni olarak alınır. Kılavuz bu kuralı açıkça belirtir: bir listedeki bütün atamalar aynı türde olmalıdır. declare -p çıktısındaki sıranın sizin yazdığınız sırayla aynı olmadığına da dikkat edin; bu konuya aşağıda değineceğiz.

Düz = ile yapılan bileşik atama diziyi baştan kurar ve önceki anahtarları siler. += ise kılavuzun ifadesiyle diziyi sıfırlamadan yeni anahtar/değer çiftleri ekler. Var olan bir anahtar +=( ) içinde tekrar geçerse değeri yenisiyle değiştirilir.

Anahtarlarda boşluk ve değişken kullanımı

Anahtar içinde boşluk olabilir. Okurken köşeli parantez içi kelime bölünmesine uğramadığı için ${renk[koyu mavi]} yazımı çalışır, ama atamada anahtarı tırnaklamak hem okunabilirlik hem de güvenlik için iyi bir alışkanlıktır. Anahtar bir değişkendeyse ${renk[$ad]} yazarsınız. Boş anahtar kullanmaya çalışırsanız Bash bad array subscript hatası verir ve atama yapılmaz.

bash
declare -A renk
renk["koyu mavi"]=00008b
ad="koyu mavi"
echo "${renk[koyu mavi]} ${renk[$ad]}"
# çıktı: 00008b 00008b

bos=""
renk[$bos]=x
echo "atama durumu: $?"
# çıktı:
#   ornek.sh: line 8: renk[$bos]: bad array subscript
#   atama durumu: 1

Anahtar dış bir kaynaktan, örneğin bir dosyadan veya kullanıcıdan geliyorsa boş olabileceğini hesaba katın ve atamadan önce [[ -n $anahtar ]] ile kontrol edin. Bu tür ön kontroller için test ve [[ ]] yazısı iyi bir başlangıç.

Anahtarlar ve değerler üzerinde dolaşmak

Anahtarlar ve değerler üzerinde dolaşmak

"${!dizi[@]}" anahtarların, "${dizi[@]}" değerlerin listesini verir. Çoğu zaman anahtarlar üzerinde döner ve değere anahtar üzerinden ulaşırsınız; böylece hangi değerin hangi anahtara ait olduğunu kaybetmezsiniz. Eleman sayısı indeksli dizide olduğu gibi ${#dizi[@]} ile alınır.

Önemli ayrıntı: Bash ilişkisel dizideki anahtarları belirli bir sırada tutmaz. Kılavuz bir sıra vaat etmez; sıra ne ekleme sırasıdır ne de alfabetik. Aynı Bash sürümünde aynı anahtarlar genellikle aynı sırada çıksa da betiğinizin mantığını bu sıraya dayandırmayın. Sıralı çıktı istiyorsanız anahtarları bir sıralama aracından geçirin.

bash
declare -A fiyat=([elma]=40 [armut]=55 [ayva]=35)

for urun in "${!fiyat[@]}"; do
  printf '%s: %s TL\n' "$urun" "${fiyat[$urun]}"
done | sort
# çıktı:
#   armut: 55 TL
#   ayva: 35 TL
#   elma: 40 TL

echo "${#fiyat[@]} ürün"
# çıktı: 3 ürün

Burada sort harici bir araçtır ve döngünün çıktısını bir boru hattıyla alır. Sıralama ölçütü yerel ayara bağlıdır; ayrıntılar için Linux komutları yazısına bakabilirsiniz. Boru hattının sağında çalışan komutların ayrı bir alt kabukta çalıştığını unutmayın: döngüyü borunun sağ tarafına koyup içinde diziyi değiştirirseniz değişiklikler kaybolur. Bu davranış pipe ve PIPESTATUS yazısında anlatılıyor.

Bir anahtar var mı? -v ve ${dizi[anahtar]+...}

Bir anahtarın değerini [[ -n ${dizi[k]} ]] ile kontrol etmek "anahtar yok" ile "anahtar var ama değeri boş" durumlarını ayırt edemez. Kılavuzun koşul ifadeleri bölümündeki -v operatörü değişkenin, dizilerde ise belirtilen elemanın atanmış olup olmadığını sınar. Alternatif olarak ${dizi[k]+x} genişletmesi yalnızca anahtar tanımlıysa x üretir.

bash
declare -A ayar=([port]=22 [parola]=)

for k in port parola kullanici; do
  if [[ -v "ayar[$k]" ]]; then
    echo "$k: tanımlı, değer='${ayar[$k]}'"
  else
    echo "$k: yok"
  fi
done
# çıktı:
#   port: tanımlı, değer='22'
#   parola: tanımlı, değer=''
#   kullanici: yok

echo "${ayar[parola]+parola anahtarı var}"
# çıktı: parola anahtarı var

-v operatörüne verilen argümanı tırnak içine alın; tırnaksız ayar[port] ifadesi teorik olarak dosya adı genişletmesine uğrayabilir. ${dizi[k]+x} yapısının iki noktalı ve iki noktasız biçimleri arasındaki fark varsayılan değer yazısında ayrıntılı anlatılıyor.

Anahtar silme ve tüm diziyi temizleme

Tek bir anahtarı unset 'dizi[anahtar]' ile silersiniz; tek tırnak burada da dosya adı genişletmesini önler. Diziyi tümüyle kaldırmak için unset dizi yazılır. Boş bir ilişkisel dizi istiyorsanız dizi=() ataması nitelikleri koruyarak tüm anahtarları temizler.

Kılavuz burada önemli bir sürüm ayrıntısına dikkat çeker: ilişkisel dizide unset 'dizi[@]' komutu bütün diziyi değil, adı tam olarak @ olan anahtarı siler. Uyumluluk modu bölümü, Bash 5.1 ve öncesinin bu ifadeyle diziyi tamamen kaldırdığını, 5.2 ile davranışın değiştiğini yazar. Eski betiklerde bu kalıbı görürseniz unset dizi veya dizi=() ile değiştirin.

bash
declare -A d=([a]=1 [b]=2 [@]=özel)
unset 'd[a]'
unset 'd[@]'            # yalnızca "@" anahtarını siler
declare -p d
# çıktı: declare -A d=([b]="2" )

d=()                     # tüm anahtarlar gider, -A niteliği kalır
d[yeni]=3
declare -p d
# çıktı: declare -A d=([yeni]="3" )

Uygulama: kelime sıklığı saymak

İlişkisel dizinin en klasik kullanımı bir şeyin kaç kez geçtiğini saymaktır. Her kelime bir anahtar, sayaç ise değer olur. Aşağıdaki örnek bir here string ile verilen kelimeleri okuyup sayıyor. Aritmetik bağlamda ((sayac[$k]++)) yazımı kısa ve okunaklıdır; aritmetik ifadeler aritmetik işlemler yazısında anlatılıyor.

bash
declare -A sayac
read -r -a kelimeler <<< "elma armut elma ayva elma armut"

for k in "${kelimeler[@]}"; do
  ((sayac[$k]++))
done

for k in "${!sayac[@]}"; do
  printf '%-6s %d\n' "$k" "${sayac[$k]}"
done | sort
# çıktı:
#   armut  2
#   ayva   1
#   elma   3

Bu örnekte anahtarlar sade kelimeler. Anahtarlar köşeli parantez, $ veya ters tırnak gibi özel karakterler içerebiliyorsa dikkatli olun: kılavuz, aritmetik değerlendirme sırasında dizi indislerinin birden fazla kez genişletilebileceğini ve bunu engellemek için bir shopt seçeneği bulunduğunu anlatır. Kılavuzun 5.3 sürümünde bu seçeneğin adı array_expand_once, eski adı assoc_expand_once ise onun eş anlamlısı olarak geçer; Bash 5.2.37 ile denediğimizde yalnızca assoc_expand_once adı tanındı. Güvenilmeyen veriyi anahtar olarak kullanıyorsanız, aritmetik bağlam yerine sayac[$k]=$(( ${sayac[$k]:-0} + 1 )) gibi düz bir atama tercih etmek daha öngörülebilirdir. Seçeneklerin genel kullanımı için shopt yazısına bakın.

Uygulama: anahtar=değer biçimindeki ayar dosyasını okumak

Basit bir ayar dosyasını satır satır okuyup ilişkisel diziye doldurmak sık karşılaşılan bir ihtiyaçtır. IFS== ile satırı ilk eşittir işaretinden ikiye bölersiniz; read fazla kelimeleri son değişkene verdiği için değerin içinde başka bir eşittir varsa o da korunur. Yorum satırlarını ve boş satırları atlamak için bir desen kontrolü ekleyebilirsiniz.

bash
printf '%s\n' '# örnek ayar' 'host=ornek.local' 'port=2222' '' 'secenek=a=b' > ayar.conf

declare -A ayar
while IFS== read -r anahtar deger; do
  [[ -z $anahtar || $anahtar == \#* ]] && continue
  ayar[$anahtar]=$deger
done < ayar.conf

echo "host=${ayar[host]} port=${ayar[port]} secenek=${ayar[secenek]}"
# çıktı: host=ornek.local port=2222 secenek=a=b
echo "${#ayar[@]} ayar okundu"
# çıktı: 3 ayar okundu

Döngü done < ayar.conf yönlendirmesiyle mevcut kabukta çalıştığı için dizi döngüden sonra da doludur. Aynı dosyayı cat ayar.conf | while ... biçiminde okusaydınız döngü bir alt kabukta çalışır ve dizi boş kalırdı. while döngüsüyle satır okumanın diğer ayrıntıları while ve until yazısında anlatılıyor.

Diziyi kaydetmek, kopyalamak ve fonksiyona aktarmak

declare -p dizi çıktısı yeniden çalıştırıldığında diziyi aynı anahtar ve değerlerle kuran geçerli bir Bash komutudur. Bu yüzden bir diziyi dosyaya kaydedip daha sonra source ile geri yüklemek mümkündür. Kılavuzun tanımladığı ${dizi[@]@K} dönüşümü de anahtar/değer çiftlerini yeniden kullanılabilir tırnaklı biçimde tek satırda verir.

Bash'te bir ilişkisel diziyi başka bir değişkene tek atamayla kopyalayan bir sözdizimi yoktur; anahtarlar üzerinde dolaşıp yeni diziye tek tek yazarsınız. Bir fonksiyonun çağıranın dizisini okuması veya değiştirmesi gerekiyorsa dizinin adını argüman olarak verip fonksiyon içinde declare -n ile isim referansı oluşturursunuz. Bu yöntem local ve kapsam yazısında örnekleriyle anlatılıyor.

bash
declare -A kaynak=([a]=1 [b]="iki kelime")
declare -A hedef
for k in "${!kaynak[@]}"; do
  hedef[$k]=${kaynak[$k]}
done
echo "${hedef[b]}"
# çıktı: iki kelime

declare -p kaynak > dizi.kayit     # diziyi dosyaya yaz
unset kaynak
source dizi.kayit                  # geri yükle
echo "${kaynak[a]} / ${kaynak[b]}"
# çıktı: 1 / iki kelime

Kaydedilen dosyayı source ile yüklemek o dosyadaki her komutu çalıştırmak demektir; bu yüzden yalnızca kendi betiğinizin ürettiği dosyalara bu şekilde güvenin. source komutunun ayrıntıları için source ve nokta komutu yazısına bakabilirsiniz.

İndeksli dizi ile ilişkisel dizi karşılaştırması

İndeksli dizi ile ilişkisel dizi karşılaştırması: Bildirim, Anahtar türü, Anahtar değerlendirmesi, Sıra
Özellikİndeksli diziİlişkisel dizi
Bildirimgerekmez (declare -a isteğe bağlı)declare -A zorunlu
Anahtar türüsıfır veya pozitif tamsayıboş olmayan herhangi bir metin
Anahtar değerlendirmesiaritmetik ifademetin olarak genişletme
Sıraindis sırasıgaranti edilmez
Negatif indissondan sayaryok
Dilimleme ${a[@]:1:2}tanımlıkılavuza göre sonucu tanımsız
unset 'a[@]'tüm elemanları siler@ adlı anahtarı siler
Eleman sayısı${#a[@]}${#a[@]}

Sık yapılan hatalar

  • declare -A yazmamak: dizi indeksli olur, metin anahtarlar 0 sayılır ve değerler birbirinin üzerine yazılır.
  • Sıraya güvenmek: anahtarların çıkış sırası ekleme sırası değildir; sıralı çıktı için sıralayın.
  • Boş değeri yok saymak: [[ -n ${a[k]} ]] boş değerli anahtarı "yok" sayar; varlık için -v kullanın.
  • Anahtarı tırnaksız silmek: unset a[k] dosya adı genişletmesine açıktır; unset 'a[k]' yazın.
  • Diziyi borunun sağında doldurmak: alt kabukta yapılan değişiklikler ana betiğe dönmez.
  • Diziyi temizlemek için unset 'a[@]': Bash 5.2 ve sonrasında bu yalnızca @ anahtarını siler.

Sık Sorulan Sorular

İlişkisel diziler hangi Bash sürümüyle kullanılabilir?

Bu yazıdaki örnekler Bash 5.2.37 ile denendi ve kılavuzun 5.3 sürümü de aynı sözdizimini anlatıyor. Çok eski sistemlerdeki sürümler hakkında elimizdeki kılavuz bir bilgi vermiyor; sisteminizde declare -A hata veriyorsa Bash sürümünü öğrenme yazısına bakın.

Anahtarları alfabetik sırayla nasıl alırım?

Bash bunu kendisi yapmaz. Anahtarları printf '%s\n' "${!dizi[@]}" ile satır satır yazdırıp bir sıralama aracından geçirebilir, sonucu mapfile -t ile yeni bir indeksli diziye okuyabilirsiniz.

Değer olarak dizi saklayabilir miyim?

Hayır. Her değer tek bir metindir. Birden fazla değeri bir anahtar altında tutmak isterseniz onları bir ayırıcıyla birleştirip saklamanız ya da [kullanici,ad] gibi birleşik anahtarlar kullanmanız gerekir.

Anahtar sayısı nasıl öğrenilir?

İndeksli dizilerde olduğu gibi ${#dizi[@]} ile. Belirli bir anahtarın değerinin uzunluğu için ${#dizi[anahtar]} yazı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 · İndeksli diziler · declare ve readonly · local ve declare -n · while read döngüsü · mapfile ve readarray