Çalışan Bash'in sürümünü öğrenmenin en doğrudan yolu echo "$BASH_VERSION" yazmaktır; ayrıntılı ve karşılaştırmaya uygun bilgi için BASH_VERSINFO dizisini, sistemdeki bash programının sürümü için bash --version komutunu kullanırsınız. Eski bir sürümün davranışına ihtiyaç duyan betikler için ise Bash bir uyumluluk modu sunar: BASH_COMPAT değişkenine 5.1 veya 51 gibi bir değer vererek bazı davranışları o sürümdeki hâline döndürebilirsiniz.

Bu yazıda sürüm bilgisini taşıyan değişkenleri, betiğin içinde güvenli sürüm kontrolü yazmayı, sürüm numarası yerine özelliğin kendisini sınamayı ve kılavuzun 6.12 bölümünde anlatılan uyumluluk seviyelerini örneklerle ele alıyoruz. Örnekler GNU Bash 5.2.37 ile çalıştırıldı; kılavuz ise 5.3 sürümünü anlatıyor. Bu iki sürüm arasındaki farkları da yalnızca kılavuzun söylediği ve bizim denediğimiz kadarıyla aktarıyoruz. Serinin diğer yazıları için Bash öğrenme rehberi sayfasına bakabilirsiniz.

BASH_VERSION ve BASH_VERSINFO

BASH_VERSION ve BASH_VERSINFO

BASH_VERSION, çalışan Bash örneğinin sürümünü anlatan bir metindir. BASH_VERSINFO ise aynı bilginin parçalara ayrılmış, salt okunur bir dizi hâlidir. Kılavuza göre elemanlar şöyle tanımlanır: 0 ana sürüm (release), 1 alt sürüm (version), 2 yama seviyesi (patch level), 3 derleme sürümü (build version), 4 yayın durumu (örneğin beta veya release) ve 5 MACHTYPE değeri. Son elemanı aşağıdaki örnekte yazdırmadık, çünkü değeri Bash'in derlendiği sisteme göre değişir:

bash
#!/usr/bin/env bash
echo "$BASH_VERSION"
# çıktı: 5.2.37(1)-release
for i in 0 1 2 3 4; do
  echo "BASH_VERSINFO[$i] = ${BASH_VERSINFO[i]}"
done
# çıktı:
#   BASH_VERSINFO[0] = 5
#   BASH_VERSINFO[1] = 2
#   BASH_VERSINFO[2] = 37
#   BASH_VERSINFO[3] = 1
#   BASH_VERSINFO[4] = release

BASH_VERSION içindeki (1) kısmı BASH_VERSINFO[3] değerine, -release kısmı da BASH_VERSINFO[4] değerine karşılık gelir. Karşılaştırma yapacaksanız metni parçalamak yerine doğrudan dizinin sayısal elemanlarını kullanın. İki değişkenin bir önemli farkı da var: BASH_VERSINFO salt okunurdur, BASH_VERSION ise değildir. Bir betik veya başlangıç dosyası yanlışlıkla BASH_VERSION değişkenine atama yaparsa, sonraki kontroller yanlış bilgiyle çalışır:

bash
#!/usr/bin/env bash
BASH_VERSINFO[0]=9
# çıktı: ornek.sh: line 2: BASH_VERSINFO: readonly variable
BASH_VERSION="9.9"
echo "$BASH_VERSION"
# çıktı: 9.9

Bu yüzden sürüm kontrolünde BASH_VERSINFO daha güvenilir bir kaynaktır.

bash --version ile farkı

bash --version, kılavuzun ifadesiyle "bu Bash örneğinin sürüm bilgisini" standart çıktıya yazar ve başarıyla çıkar. Ancak bu komut, o anda içinde bulunduğunuz kabuğu değil, PATH içinde bulunan bash programını yeni bir süreç olarak başlatır. Bir sunucuda birden fazla Bash kurulu olabilir; örneğin sistemle gelen sürüm ile sonradan farklı bir dizine kurulan sürüm. Bu durumda bash --version çıktısı ile betiğin içindeki $BASH_VERSION farklı olabilir. Betiğinizi hangi programın çalıştırdığını görmek için $BASH değişkenine bakın; kılavuza göre bu değişken, çalışan Bash örneğini başlatmak için kullanılan tam yolu tutar.

bash
$ bash --version
# İlk satırda sürüm dizesi yer alır; geri kalan satırlar telif ve lisans bilgisidir.
$ echo "$BASH_VERSION"
# Şu anda içinde bulunduğunuz kabuğun sürümünü yazar.

Sık yapılan bir yanılgı da $SHELL değişkenine bakarak çalışan kabuğu anlamaya çalışmaktır. Kılavuza göre SHELL, kabuğun tam yolunu tutan bir ortam değişkenidir ve kabuk başlarken tanımlı değilse Bash ona kullanıcının giriş kabuğunun yolunu atar. Yani bir betiği hangi kabukla çalıştırırsanız çalıştırın, $SHELL genellikle aynı kalır ve çalışan kabuğu göstermez.

Betiğin içinde sürüm kontrolü

Betiğin içinde sürüm kontrolü

Betiğiniz belirli bir sürümle gelen bir özelliğe dayanıyorsa, en başta bir kontrol yapıp anlaşılır bir mesajla çıkmak, ortada bir yerde anlamsız bir sözdizimi hatası almaktan iyidir. Ana ve alt sürüm numaralarını aritmetik bir ifadeyle karşılaştırabilirsiniz:

bash
#!/usr/bin/env bash
en_az() {
  local ana=$1 alt=$2
  (( BASH_VERSINFO[0] > ana || (BASH_VERSINFO[0] == ana && BASH_VERSINFO[1] >= alt) ))
}
if en_az 4 3; then
  echo "declare -n (nameref) kullanılabilir"
fi
# çıktı: declare -n (nameref) kullanılabilir
if ! en_az 5 3; then
  echo "5.3 gerektiren özellikler için yedek yol kullanılacak"
fi
# çıktı: 5.3 gerektiren özellikler için yedek yol kullanılacak

Fonksiyon çıkış kodunu doğrudan (( )) ifadesinden alıyor: ifade doğruysa 0, yanlışsa 1 döner ve if buna göre dallanır. Dizinin indislerinde $ işareti yazmadığımıza dikkat edin; aritmetik bağlamda değişken adları zaten değerlerine çevrilir. Aritmetik ifadelerin kuralları için Bash aritmetik işlemler yazısına bakın.

Önce Bash'in kendisiyle çalışıp çalışmadığınızı sınamak da isteyebilirsiniz. Biri betiğinizi sh betik.sh ile çalıştırırsa ve /bin/sh başka bir kabuğa bağlıysa, BASH_VERSION tanımlı olmaz. Aşağıdaki kontrol yalnızca POSIX yapıları kullandığı için o kabukta da hatasız çalışıp mesajını yazabilir:

bash
#!/usr/bin/env bash
if [ -z "${BASH_VERSION-}" ]; then
  echo "Bu betik bash ile çalıştırılmalı." >&2
  exit 1
fi
echo "bash altında çalışıyoruz"
# çıktı: bash altında çalışıyoruz

Bash'i sh adıyla çağırdığınızda bu değişken yine tanımlı olur, çünkü çalışan program Bash'tir; Bash yalnızca POSIX moduna geçer. İkisi arasındaki farklar için Bash ve sh farkı yazısına bakabilirsiniz.

Sürüm yerine özelliği sınamak

Sürüm numarası, ihtiyaç duyduğunuz şeyin yalnızca dolaylı bir göstergesidir. Asıl bilmek istediğiniz, kullanacağınız seçeneğin veya değişkenin o kabukta bulunup bulunmadığıdır; bu doğrudan sınanabiliyorsa sürüm tablosu ezberlemeye gerek kalmaz. shopt seçenekleri için bu çok kolaydır, çünkü geçersiz bir seçenek adı hata koduyla döner:

bash
#!/usr/bin/env bash
if shopt -p globskipdots &>/dev/null; then
  echo "globskipdots seçeneği var"
fi
# çıktı: globskipdots seçeneği var
if ! shopt -p array_expand_once &>/dev/null; then
  echo "array_expand_once bu bash'te yok"
fi
# çıktı: array_expand_once bu bash'te yok

Aynı yaklaşım değişkenler için de çalışır. Kılavuz 5.3 bazı yeni değişkenler ve seçenekler anlatıyor; denediğimiz 5.2.37'de bunlar yoktu. Tanımsız bir özel değişken sıradan bir tanımsız değişken gibi davrandığı için varsayılan değer genişletmesiyle kolayca sınanabilir:

bash
#!/usr/bin/env bash
echo "BASH_MONOSECONDS: ${BASH_MONOSECONDS-tanımsız}"
# çıktı: BASH_MONOSECONDS: tanımsız
echo "SRANDOM tanımlı mı: ${SRANDOM:+evet}"
# çıktı: SRANDOM tanımlı mı: evet
if shopt -p bash_source_fullpath &>/dev/null; then
  echo "bash_source_fullpath var"
else
  echo "bash_source_fullpath yok"
fi
# çıktı: bash_source_fullpath yok

Sözdizimine dayanan yeni özellikleri ise bu şekilde sınayamazsınız, çünkü eski bir sürüm o satırı ayrıştıramaz. Örneğin kılavuz (5.3), komutu alt kabuk açmadan mevcut kabukta çalıştıran ${ komut; } biçiminde bir komut ikamesi anlatıyor; 5.2.37 ile denediğimizde çalışmadı ve bad substitution hatası verdi. Böyle bir yapıyı ancak sürüm kontrolünden sonra, ayrı bir dosyayı source ederek ya da eval ile çalıştırabilirsiniz:

bash
# Yalnızca Bash 5.3 kılavuzunda anlatılıyor; 5.2.37'de "bad substitution" verdi (çıktı gösterilmiyor)
x=${ echo merhaba; }

Kılavuz (5.3) ayrıca GLOBSORT, BASH_TRAPSIG, array_expand_once ve bash_source_fullpath gibi öğeler de belgeliyor; bunlar da denediğimiz sürümde bulunmuyordu. Komut ikamesinin bugünkü biçimleri için komut ikamesi yazısına bakın.

Uyumluluk modu: BASH_COMPAT ve compat seçenekleri

Uyumluluk modu: BASH_COMPAT ve compat seçenekleri: Örnek 1: compat51 ve unset a[@], Örnek 2: compat43 ve fonksiyon içinden break

Kılavuzun 6.12 bölümüne göre kabuk uyumluluk seviyesi kavramı Bash 4.0 ile geldi. Amaç, yeni sürümlerle uyumsuz hâle gelmiş eski davranışları, betikler güncellenene kadar geçici olarak geri getirmektir. Kılavuz bunu açıkça "geçici bir çözüm" olarak tanımlar. Aynı anda yalnızca bir uyumluluk seviyesi etkin olabilir.

Seviye iki yolla seçilir. İlki compat31, compat32, compat40, compat41, compat42, compat43 ve compat44 adlı shopt seçenekleridir. İkincisi Bash 4.3 ile gelen BASH_COMPAT değişkenidir; değeri 4.2 gibi ondalıklı bir sürüm ya da 42 gibi bir tamsayı olabilir. Kılavuz, Bash 4.4 ile eski seviyelerin kullanımdan kaldırılmaya başlandığını, Bash 5.0'ın bir önceki sürüm için ayrı shopt seçeneği bulunan son sürüm olduğunu ve 5.0'dan yeni sürümlerde seviyeyi denetlemenin tek yolunun BASH_COMPAT olduğunu belirtir. Denediğimiz sürümde compat44 seçeneği hâlâ çalışıyordu ve açıldığında BASH_COMPAT değerini de değiştirdi:

bash
#!/usr/bin/env bash
shopt -s compat44
echo "BASH_COMPAT = $BASH_COMPAT"
# çıktı: BASH_COMPAT = 44
shopt compat44
# çıktı: compat44       	on

BASH_COMPAT boş bırakılır veya kaldırılırsa seviye geçerli sürümün varsayılanına döner. Geçersiz bir değer verilirse kabuk bir hata mesajı yazar ve seviyeyi yine varsayılana ayarlar. Denediğimiz sürümde atamanın kendisi 0 döndü, yani betik bu hatada durmadı:

bash
#!/usr/bin/env bash
BASH_COMPAT=99
echo "\$? = $?"
# çıktı:
#   ornek.sh: line 2: BASH_COMPAT: 99: compatibility value out of range
#   $? = 0

Kılavuz, geçerli değerlerin bir kısmının aşağıda anlatılan seviyelere karşılık geldiğini ve kabuğun kendi güncel sürümünün de geçerli bir değer olduğunu belirtir. Örneğin 4.2 ve 42 aynı seviyeyi seçer ve compat42 seçeneğiyle aynı anlama gelir. Seviyeyi kod okuyan biri için açık olması adına betiğin en üstünde, kısa bir açıklama yorumuyla birlikte atamanızı öneririz; böylece betik güncellendiğinde satırın kaldırılması unutulmaz.

Örnek 1: compat51 ve unset a[@]

Kılavuzun compat51 maddesine göre 5.1 seviyesinde unset 'a[@]' dizinin tamamını kaldırır. 5.2 ise indisli bir dizide tüm elemanları siler ama diziyi tanımlı bırakır (ilişkisel dizide @ adlı anahtarı siler). Fark declare -p çıktısında açıkça görülüyor:

bash
#!/usr/bin/env bash
a=(1 2 3)
unset 'a[@]'
declare -p a
# çıktı: declare -a a=()
BASH_COMPAT=51
b=(1 2 3)
unset 'b[@]'
declare -p b
# çıktı: ornek.sh: line 9: declare: b: not found

Varsayılan seviyede a boş bir dizi olarak kaldı; BASH_COMPAT=51 sonrasında b tamamen kayboldu. Dizilerle ilgili diğer ayrıntılar için Bash dizi yazısına bakın.

Örnek 2: compat43 ve fonksiyon içinden break

Kılavuza göre 4.3 seviyesinde bir fonksiyon çalışırken döngü durumu sıfırlanmaz; bu yüzden fonksiyondaki break veya continue, çağıran döngüyü etkiler. Bash 4.4 ve sonrası döngü durumunu sıfırlar. Aşağıdaki örnekte varsayılan seviyede break hata verir ve döngü sürer; BASH_COMPAT=43 ile ise fonksiyon çağıranın döngüsünü kırar:

bash
#!/usr/bin/env bash
dur() { break; }
for i in 1 2; do
  dur
  echo "varsayılan: $i"
done
# çıktı:
#   ornek.sh: line 2: break: only meaningful in a `for', `while', or `until' loop
#   varsayılan: 1
#   ornek.sh: line 2: break: only meaningful in a `for', `while', or `until' loop
#   varsayılan: 2
BASH_COMPAT=43
for i in 1 2 3; do
  dur
  echo "compat43: $i"
done
echo "döngü fonksiyon içinden kırıldı"
# çıktı: döngü fonksiyon içinden kırıldı

Bu tür davranışlara dayanan eski bir betiği hemen düzeltemiyorsanız uyumluluk seviyesi zaman kazandırır. Ama kalıcı çözüm, fonksiyonun bir çıkış kodu döndürmesi ve kararı döngünün kendisinin vermesidir: dur || break.

Seviyelerin özeti

Aşağıdaki tablo, kılavuzun her seviye için saydığı değişikliklerden bir seçki sunuyor. Kılavuz, bir seviyeyi açmanın kendisinden önceki seviyelerin davranışlarını da etkileyebileceğini ve bu ayrıntı düzeyinin her kullanım için yeterli olmayabileceğini, bu yüzden seviyelerin dikkatle kullanılması gerektiğini vurgular:

SeviyeEski davranış (seçki)
compat31[[ =~ ]] sağ tarafını tırnaklamanın özel bir etkisi yok
compat40[[ ]] içindeki < ve > yerel ayar yerine ASCII sırasıyla karşılaştırır
compat41POSIX modunda time seçeneklerle birlikte de anahtar kelime sayılır
compat42Çift tırnaklı desen değiştirmede yeni metin tırnak kaldırmadan geçmez
compat43Genişletme hataları POSIX modunda bile ölümcül değil; fonksiyonda döngü durumu sıfırlanmaz
compat44Alt kabuk üst döngüyü devralır; BASH_ARGV ve BASH_ARGC extdebug olmadan da hazırlanır
compat50RANDOM 5.0 yöntemiyle üretilir; aynı tohum 5.0 ile aynı diziyi verir
compat51unset a[@] dizinin tamamını kaldırır; bazı aritmetik ifadeler birden çok kez genişletilebilir
compat52test, beş ve daha fazla argümanda parantezleri tarihsel algoritmayla ayrıştırır

Tablodaki compat50, compat51 ve compat52 seviyeleri için ayrı bir shopt seçeneği yoktur; kılavuz bunları "BASH_COMPAT ile ayarlanır" diye işaretler. Ayrıca kılavuz, bir sürüm için zaten standart olan davranışların bu bölümde anlatılmadığını not eder.

Kılavuzun söylediği sürüm geçişleri

Seri boyunca bir özelliğin "hangi sürümde geldiğini" yalnızca kılavuz açıkça söylüyorsa yazıyoruz. Kılavuzun 6.12 bölümünde ve değişken açıklamalarında geçen bazı geçişler şunlar: [[ ]] içinde yerel ayara dayalı karşılaştırma Bash 4.1 ile geldi, öncesinde ASCII sıralaması kullanılıyordu; Bash 4.4 fonksiyon çağrılarında döngü durumunu sıfırlamaya başladı; Bash 5.0 alt kabuklarda döngü durumunu sıfırladı; Bash 5.1 $RANDOM üretimini değiştirdi; Bash 5.2 unset a[@] davranışını değiştirdi. Bunların dışındaki sürüm farkları için kendi sisteminizde deneme yapmanızı ve belge sürümünü kontrol etmenizi öneririz.

Sık yapılan hatalar

  • $SHELL ile çalışan kabuğu anlamaya çalışmak: Giriş kabuğunu gösterir; $BASH_VERSION veya $BASH kullanın.
  • bash --version ile betiğin sürümünü sanmak: PATH içindeki programı çalıştırır, çalışan kabuğu değil.
  • BASH_VERSION metnini karşılaştırmak: Metin karşılaştırması sürüm sırasını doğru vermeyebilir; BASH_VERSINFO sayılarını kullanın.
  • Uyumluluk modunu kalıcı çözüm saymak: Kılavuz bunu geçiş dönemi için düşünülmüş geçici bir çözüm olarak tanımlar.
  • 5.3 sözdizimini eski sürümde denemek: Ayrıştırma aşamasında hata verir; özellik sınaması yapamazsınız, önce sürüm kontrolü yapın.

Sık Sorulan Sorular

Bash sürümümü nasıl öğrenirim?

Etkileşimli kabukta echo "$BASH_VERSION" yazın. Sistemdeki bash programının sürümü için bash --version kullanın.

BASH_COMPAT değişkenini export etmeli miyim?

Seviye yalnızca o kabuk için gerekiyorsa betiğin içinde atamak yeterlidir. Denememizde ortamdan BASH_COMPAT=51 ile başlatılan yeni bir Bash süreci de 5.1 davranışını gösterdi; yani değişkeni export ederseniz alt Bash süreçleri de etkilenir. Bunu yalnızca gerçekten ihtiyaç olduğunda yapın.

Uyumluluk modu POSIX moduyla aynı mı?

Hayır. POSIX modu Bash'i standarda yaklaştırır; uyumluluk modu ise Bash'in kendi eski sürümlerinin davranışını geri getirir. Bazı seviyelerin yalnızca POSIX modunda etkisi vardır.

Kodum 5.2 ile çalışıyor, 5.3 ile de çalışır mı?

Çoğu betik için davranış aynıdır, ama garanti etmek için hedef sürümle test etmeniz gerekir. Kılavuzun compat52 maddesi, 5.3'te değişen davranışlardan bazılarını (örneğin test komutunun beş ve daha fazla argümanı ayrıştırması) listeler.

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 · Bash ve sh farkı · shopt seçenekleri · komut ikamesi · sık hata mesajları · Bash diziler