Bash'te bir değişken boşsa ya da hiç tanımlanmamışsa yerine başka bir değer kullanmak için ${degisken:-varsayilan} yazarsınız. Aynı ailede dört genişletme vardır: :- yedek değeri yalnızca kullanır, := yedek değeri değişkene de atar, :? değişken boşsa hata mesajı basıp betiği durdurur, :+ ise değişken doluysa verdiğiniz metni koyar. İki noktayı yazmazsanız (-, =, ?, +) Bash yalnızca değişkenin tanımlı olup olmadığına bakar, boş değeri geçerli kabul eder.

Bu yazıda dört genişletmeyi ve iki nokta farkını tablo ve örneklerle anlatıyor, konumsal parametrelere atama kısıtını, :? ile zorunlu argüman kontrolünü, ortam değişkeniyle ayar geçersiz kılmayı, set -u ile birlikte kullanımı ve sık yapılan ${s:-3} ile ${s: -3} karışıklığını GNU Bash kılavuzuna dayanarak ele alıyoruz. Bu yazı parametre genişletmenin ilk yarısını kapsıyor; uzunluk, kesme ve değiştirme işlemleri Bash string işlemleri yazısında. Serinin tamamı için Bash öğrenme rehberi sayfasına bakabilirsiniz. Örnekler GNU Bash 5.2.37 ile çalıştırıldı.

Ayarsız, boş ve dolu: üç durum

Ayarsız, boş ve dolu: üç durum: ${v:-w}, ${v-w}, ${v:=w}, ${v=w}

Bu genişletmeleri anlamanın anahtarı, bir değişkenin üç farklı durumda olabileceğini bilmektir. Kılavuz, bir parametreye değer atandığında ayarlanmış (set) sayıldığını ve boş metnin de geçerli bir değer olduğunu söyler. Yani bos= satırından sonra bos değişkeni tanımlıdır ama değeri boştur. Hiç atanmamış ya da unset ile silinmiş bir değişken ise ayarsızdır (unset). Üçüncü durum, boş olmayan bir değere sahip dolu değişkendir.

Kılavuzdaki kural şöyle: iki nokta varsa operatör hem değişkenin var olup olmadığını hem de değerinin boş olup olmadığını test eder; iki nokta yoksa yalnızca var olup olmadığına bakar. Aşağıdaki örnek dört operatörün üç durumda ne ürettiğini yan yana gösteriyor:

bash
dolu=deger
bos=
unset yok
echo ":-  [${dolu:-yedek}] [${bos:-yedek}] [${yok:-yedek}]"
echo "-   [${dolu-yedek}] [${bos-yedek}] [${yok-yedek}]"
echo ":+  [${dolu:+var}] [${bos:+var}] [${yok:+var}]"
echo "+   [${dolu+var}] [${bos+var}] [${yok+var}]"
# çıktı:
#   :-  [deger] [yedek] [yedek]
#   -   [deger] [] [yedek]
#   :+  [var] [] []
#   +   [var] [var] []

İlk iki satırın farkı tam olarak iki noktadan geliyor: ${bos:-yedek} boş değeri yetersiz bulup yedeği koyuyor, ${bos-yedek} ise boş değeri kabul ediyor. Son iki satırda aynı ayrım + için görünüyor. Pratikte çoğu zaman iki noktalı biçim istenir, çünkü boş bir yapılandırma değeri genellikle verilmemiş bir değerle aynı anlama gelir. Kullanıcının değişkeni bilerek boş bırakabilmesini istiyorsanız iki noktasız biçimi seçersiniz.

YazımDeğişken doluDeğişken boşDeğişken ayarsız
${v:-w}değerww
${v-w}değerboşw
${v:=w}değerw atanır ve kullanılırw atanır ve kullanılır
${v=w}değerboşw atanır ve kullanılır
${v:?m}değerhata m, betik çıkarhata m, betik çıkar
${v?m}değerboşhata m, betik çıkar
${v:+w}wboşboş
${v+w}wwboş

:- ile yedek değer kullanmak

${v:-w} en sık kullanılan biçimdir. Değişkeni değiştirmez, yalnızca o noktada hangi metnin kullanılacağını belirler. Yapılandırma değerlerinin dışarıdan verilebildiği betiklerde özellikle kullanışlıdır: kullanıcı bir ortam değişkeni tanımladıysa o kullanılır, tanımlamadıysa makul bir varsayılan devreye girer.

bash
unset PORT
yapilandir() {
  local port=${PORT:-8080}
  echo "port: $port"
}
yapilandir                    # çıktı: port: 8080
PORT=9090 yapilandir          # çıktı: port: 9090
echo "sonra: [${PORT-ayarsız}]"   # çıktı: sonra: [ayarsız]

İkinci çağrıda PORT=9090 yapilandir yazdık. Bir komutun önüne yazılan atama yalnızca o komut için geçerlidir; kılavuzun Bourne kabuğu farkları ekinde, komutlardan önce gelen atamaların fonksiyonlar ve yerleşik komutlar dahil yalnızca o komutu etkilediği belirtilir. Bu yüzden son satırda PORT yine ayarsız. Gerçek kullanımda betiği PORT=9090 ./sunucu.sh biçiminde çağırırsınız ve betik içindeki ${PORT:-8080} ifadesi bu değeri alır.

Kılavuz, word kısmının tilde, parametre, komut ikamesi ve aritmetik genişletmeden geçtiğini söyler. Yani yedek değer sabit bir metin olmak zorunda değildir; başka bir değişken ya da bir komutun çıktısı olabilir. Denediğimizde yedek ifade yalnızca gerçekten gerektiğinde çalıştı: değişken doluyken komut ikamesi hiç çalışmadı.

bash
pahali() { echo "pahali() çalıştı" >&2; echo hesaplandı; }

deger=hazır
echo "${deger:-$(pahali)}"    # çıktı: hazır
unset deger
echo "${deger:-$(pahali)}"
# çıktı:
#   pahali() çalıştı
#   hesaplandı

Bu davranış pahalı bir hesaplamayı ya da bir dosya okumayı yalnızca ihtiyaç olduğunda yapmanızı sağlar. Genişletmeler iç içe de yazılabilir: ${A:-${B:-sabit}} önce A, sonra B değişkenine bakar, ikisi de boşsa sabiti kullanır. Aşağıdaki örnekte bunu ve :+ ile yapılan bir birleştirme kalıbını görebilirsiniz.

:= ile varsayılanı değişkene atamak

${v:=w} yedeği kullanmakla kalmaz, değişkene de atar. Böylece betiğin geri kalanında değişkeni tekrar kontrol etmeniz gerekmez. Tek başına atama yapmak için genellikle hiçbir şey yapmayan : yerleşik komutuyla birlikte kullanılır; : argümanlarını genişletir ve 0 döndürür. Genişletmeyi çift tırnağa almak, yedek değerin içinde boşluk veya joker karakter olduğunda gereksiz sözcük bölmeyi ve dosya adı genişletmeyi önler.

bash
unset port
echo "${port:=8080}"           # çıktı: 8080
echo "port şimdi: $port"       # çıktı: port şimdi: 8080

: "${LOG_DIZIN:=./log}"        # yalnızca atama için : komutu
echo "$LOG_DIZIN"              # çıktı: ./log

LOG_DIZIN=/srv/log
: "${LOG_DIZIN:=./log}"
echo "$LOG_DIZIN"              # çıktı: /srv/log

Son kısımda LOG_DIZIN zaten dolu olduğu için atama yapılmadı ve mevcut değer korundu. Betiklerin başında bir dizi : "${AYAR:=varsayilan}" satırı yazmak, hangi ayarların dışarıdan değiştirilebildiğini ve varsayılanlarının ne olduğunu tek bakışta gösterir. Kılavuz, bu yolla konumsal parametrelere ve özel parametrelere atama yapılamayacağını açıkça söyler:

bash
f() { : "${1:=varsayilan}"; }
f
echo "durum: $?"
# çıktı:
#   ornek.sh: line 1: $1: cannot assign in this way
#   durum: 1

Fonksiyon argümanına varsayılan vermek için := yerine :- ile yerel bir değişkene atayın: local hedef=${1:-./yedek}. Konumsal parametrelerin değerini toplu değiştirmek gerekiyorsa set -- kullanılır; bu konu Bash script argümanları yazısında.

:? ile zorunlu değer kontrolü

:? ile zorunlu değer kontrolü

${v:?mesaj} değişken boş ya da ayarsızsa mesajı standart hataya yazar. Kılavuza göre etkileşimli olmayan bir kabuk, yani bir betik, bu durumda sıfır dışı bir durumla çıkar. Etkileşimli kabuk çıkmaz ama genişletmenin bulunduğu komutu çalıştırmaz. Bu, zorunlu argümanları tek satırda kontrol etmenin kısa bir yoludur:

bash
yedekle() {
  local kaynak=${1:?kaynak dizin gerekli}
  echo "$kaynak yedekleniyor"
}
yedekle ./veri         # çıktı: ./veri yedekleniyor
yedekle
echo "bu satır çalışmaz"
# çıktı: ornek.sh: line 2: 1: kaynak dizin gerekli

Hata satırında Bash, değişkenin adını (burada konumsal parametre 1) ve sizin mesajınızı yazıyor, ardından betiği bitiriyor; son echo hiç çalışmıyor. Örnek 1 durumuyla bitti. Mesaj kısmını boş bırakırsanız Bash kendi standart mesajını kullanır:

bash
hedef=
: "${hedef:?}"
# çıktı: ornek.sh: line 2: hedef: parameter null or not set

:? kısa ve etkili olsa da kullanıcıya gösterilen mesaj üzerinde sınırlı kontrol sağlar: satır numarası ve değişken adı her zaman eklenir, çıkış kodunu seçemezsiniz. Kullanıcı dostu bir kullanım mesajı ve özel bir çıkış kodu istiyorsanız if [[ -z ${1-} ]]; then ... exit 2; fi gibi açık bir kontrol daha uygundur. Koşul ifadeleri test ve [[ ]] farkı yazısında, çıkış kodları Bash exit kodları yazısında.

:+ ile koşullu metin eklemek

${v:+w} diğerlerinin tersidir: değişken doluysa w metnini koyar, boş ya da ayarsızsa hiçbir şey koymaz. Kılavuzun vurguladığı gibi değişkenin kendi değeri kullanılmaz. Bu, ayırıcı eklemek veya bir seçeneği yalnızca bir değer verildiğinde komuta eklemek için idealdir:

bash
liste=
for meyve in elma armut kiraz; do
  liste=${liste:+$liste,}$meyve
done
echo "$liste"                     # çıktı: elma,armut,kiraz

ad=Zeynep
echo "${ad:+Merhaba $ad!}"        # çıktı: Merhaba Zeynep!
echo "${A:-${B:-sabit değer}}"    # çıktı: sabit değer

İlk döngüde ${liste:+$liste,} ifadesi, liste henüz boşken hiçbir şey üretmiyor; dolu olduğunda mevcut listeyi ve bir virgülü üretiyor. Böylece başta ya da sonda fazladan virgül kalmıyor. İkinci satır, bir ad verilmişse selamlama, verilmemişse boş çıktı üretiyor. Komut satırı seçenekleri kurarken de aynı kalıp kullanılır, ancak birden fazla sözcük üretecekseniz dizi kullanmak daha güvenlidir; bkz. Bash dizi kullanımı.

set -u ile birlikte kullanmak

set -u (nounset) açıkken ayarsız bir değişkeni genişletmek hatadır ve betik durur. Bu seçenek yazım hatalarını erken yakalamak için değerlidir, ama isteğe bağlı argümanlar ve ortam değişkenleri için sorun çıkarır. Çözüm yine bu ailedendir: ${1:-yok} ya da yalnızca hatayı susturmak için ${1-} yazdığınızda Bash değişkenin ayarsız olmasını hata saymaz.

bash
set -u
echo "ilk argüman: [${1:-yok}]"   # çıktı: ilk argüman: [yok]
echo "$1"
# çıktı: ornek.sh: line 3: $1: unbound variable

İkinci satır yedek değer sayesinde çalıştı, üçüncü satır ise unbound variable hatasıyla betiği durdurdu. set -euo pipefail kullanan betiklerde isteğe bağlı her değişkene ${VAR:-} yazmak yaygın bir alışkanlıktır. Bu seçeneklerin bütün ayrıntıları set -euo pipefail ile güvenli betik yazısında.

Karıştırılan yazım: ${s:-3} ve ${s: -3}

Aynı süslü parantezler alt metin almak için de kullanılır: ${s:ofset} metnin belirli bir konumdan sonrasını verir. Negatif ofsetle sondan saymak istediğinizde iki nokta ile eksi işareti yan yana gelir ve Bash bunu :- varsayılan değer operatörü olarak okur. Kılavuz bu yüzden negatif ofsetin iki noktadan en az bir boşlukla ayrılması gerektiğini söyler. Parantez kullanmak da aynı sonucu verir:

bash
s=abcdef
echo "${s:-3}"     # çıktı: abcdef
echo "${s: -3}"    # çıktı: def
echo "${s:(-3)}"   # çıktı: def

İlk satırda s dolu olduğu için varsayılan değer operatörü değerin kendisini döndürdü; hata mesajı yok, sadece beklenmeyen bir sonuç var. Bu yüzden bu hata fark edilmeden kalabilir. Alt metin işlemlerinin tamamını Bash string işlemleri yazısında anlatıyoruz.

Uygulamalı örnek: yapılandırılabilir bir yedekleme betiği

Dört genişletmeyi tek bir betiğin başında nasıl kullanabileceğinizi gösteren bir iskelet aşağıda. Örneği tek dosyada çalıştırabilmek için betiğe argüman verilmiş gibi davranan bir set -- satırı ve bir ETIKET ataması ekledik; gerçek betikte bu iki satır olmaz, değerler komut satırından ve ortamdan gelir:

bash
set -u
set -- ./veri          # betiğe ./veri argümanı verilmiş gibi
ETIKET=gunluk          # dışarıdan gelmiş bir ortam değişkeni gibi

kaynak=${1:?kullanım: yedekle.sh <kaynak-dizin> [hedef-dizin]}
hedef=${2:-./yedekler}
: "${SIKISTIRMA:=gzip}"
etiket=${ETIKET:+_$ETIKET}

echo "kaynak=$kaynak hedef=$hedef sıkıştırma=$SIKISTIRMA"
echo "dosya: yedek${etiket}.tar"
# çıktı:
#   kaynak=./veri hedef=./yedekler sıkıştırma=gzip
#   dosya: yedek_gunluk.tar

Birinci argüman zorunlu ve verilmezse betik kullanım mesajıyla duruyor. İkinci argüman isteğe bağlı ve verilmezse ./yedekler kullanılıyor. SIKISTIRMA ortam değişkeni dışarıdan verilebiliyor, verilmezse gzip atanıyor ve betiğin geri kalanı bu değişkene güvenebiliyor. ETIKET verildiği için dosya adına alt çizgiyle eklendi; verilmeseydi dosya adı yedek.tar olarak sade kalırdı. set -u açık olduğu halde hiçbir satır hata vermiyor, çünkü her isteğe bağlı değişken bir yedek ile korunuyor.

Sık yapılan hatalar

Sık yapılan hatalar: İki noktayı unutmak, :- kullanıp değişkenin de değiştiğini sanmak, ${1:=x} ile konumsal parametreye atamaya çalışmak, Atama için
  • İki noktayı unutmak: ${v-w} boş değeri kabul eder, boşken de yedek istiyorsanız ${v:-w} yazın.
  • :- kullanıp değişkenin de değiştiğini sanmak: yalnızca := atama yapar.
  • ${1:=x} ile konumsal parametreye atamaya çalışmak: cannot assign in this way hatası verir.
  • Atama için : komutunu kullanırken genişletmeyi tırnağa almamak: yedek değerdeki joker karakterler dosya adlarına genişleyebilir.
  • Negatif ofseti ${s:-3} diye yazmak: bu varsayılan değer operatörüdür; ${s: -3} yazın.
  • set -u açıkken isteğe bağlı argümanları korumasız bırakmak: ${1-} veya ${1:-varsayilan} kullanın.

Sık Sorulan Sorular

Aynı işi if ile yazmaktan farkı ne?

hedef=${2:-./yedekler} satırının uzun karşılığı, önce [[ -z ${2-} ]] ile boşluğu test edip iki daldan birinde atama yapan beş satırlık bir if bloğudur. Sonuç aynıdır; genişletme yalnızca daha kısadır ve atamayla kontrolü tek yerde toplar. Koşul içinde başka işler de yapmanız gerekiyorsa, örneğin bir uyarı basmak ya da günlüğe yazmak istiyorsanız açık if bloğu daha okunaklı olur. İkisini karıştırmak yerine betik boyunca tutarlı olmak, okuyan kişinin işini kolaylaştırır.

Değişkenin boş mu ayarsız mı olduğunu nasıl ayırt ederim?

${v+x} değişken ayarlıysa (boş bile olsa) x, ayarsızsa hiçbir şey üretir. Koşul olarak [[ -v v ]] testi de değişkenin ayarlı olup olmadığını söyler; bu test test ve [[ ]] yazısında.

Varsayılan değer bir dizi olabilir mi?

Bu genişletmeler tek bir metin üretir. Dizinin tamamı boşsa yedek kullanmak için "${dizi[@]:-yedek}" yazılabilir, ama bu tek elemanlı bir sonuç verir. Diziye varsayılan elemanlar atamak için önce eleman sayısını ${#dizi[@]} ile kontrol etmek daha açıktır.

:? hatasını yakalayıp betiğin devam etmesini sağlayabilir miyim?

Betik içinde doğrudan değil; kılavuza göre etkileşimli olmayan kabuk çıkar. Kontrolü bir alt kabukta yaparsanız yalnızca alt kabuk biter: ( : "${v:?}" ) || echo eksik. Ancak çoğu durumda açık bir if kontrolü daha okunaklıdır.

Bu genişletmeler sh ile de çalışır mı?

Bu yazıdaki dört operatör kılavuzun parametre genişletme bölümünde temel biçimler olarak yer alır. Betiğinizin hangi kabukta çalışacağından emin değilseniz Bash ve sh farkı yazısına bakın.

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 · String işlemleri · Değişkenler ve atama · set -euo pipefail · Betik argümanları · test ve [[ ]]