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?

İ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.
# 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=ff0000declare -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.
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=3Karışı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.
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: 1Anahtar 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

"${!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.
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ünBurada 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.
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.
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.
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 3Bu ö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.
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 okunduDö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.
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 kelimeKaydedilen 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ı

| Özellik | İndeksli dizi | İlişkisel dizi |
|---|---|---|
| Bildirim | gerekmez (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ğerlendirmesi | aritmetik ifade | metin olarak genişletme |
| Sıra | indis sırası | garanti edilmez |
| Negatif indis | sondan sayar | yok |
| 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 -Ayazmamak: 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-vkullanı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