shopt, Bash'in isteğe bağlı davranışlarını açıp kapatmanızı sağlayan yerleşik (builtin) komuttur. shopt -s ad bir seçeneği açar, shopt -u ad kapatır, shopt -q ad hiçbir şey yazdırmadan seçeneğin durumunu çıkış koduyla bildirir, shopt -p ise seçenekleri yeniden çalıştırılabilir komutlar hâlinde listeler. Eşleşmeyen joker desenlerin kaybolması (nullglob), ** ile alt dizinlerin taranması (globstar) veya case içinde büyük/küçük harf ayrımının kalkması (nocasematch) gibi ayarların hepsi bu komutla yapılır.

Bu yazıda shopt komutunun bayraklarını, set -o ile arasındaki farkı, betiklerde en çok işe yarayan seçenekleri ve bir seçeneği geçici olarak değiştirip eski hâline döndürmenin güvenli yolunu anlatıyoruz. Joker desenlerin kendisi için Bash joker karakterler rehberine, genişletilmiş desenler için extglob yazısına, set -e ve benzeri güvenlik seçenekleri için set -euo pipefail rehberine bakabilirsiniz. Tüm seriye Bash öğrenme rehberi üzerinden ulaşabilirsiniz.

shopt ne işe yarar?

Bash'te kabuğun nasıl davranacağını belirleyen iki ayrı seçenek tablosu vardır. Birincisi set komutuyla yönetilen, çoğu POSIX standardından gelen seçeneklerdir: errexit, nounset, noclobber, pipefail gibi. İkincisi Bash'e özgü, daha ince ayar sunan ve shopt (shell options) komutuyla yönetilen tablodur. Kılavuz, aksi belirtilmedikçe shopt seçeneklerinin varsayılan olarak kapalı olduğunu söyler; checkwinsize, cmdhist, extquote, globskipdots, sourcepath ve patsub_replacement gibi birkaç tanesi ise açık gelir.

Bu seçenekler kabuğun çalışma ortamının (execution environment) parçasıdır. Yani bir betikte açtığınız seçenek, o betiğin parantezle açtığı alt kabuklara (subshell) da geçer; ama betiğin başlattığı yeni bir bash süreci varsayılan ayarlarla başlar. Bir seçeneği komut satırından açtınız diye, çalıştırdığınız betik onu görmez. Betiğiniz hangi seçeneğe ihtiyaç duyuyorsa onu betiğin kendi içinde, genellikle en üstte açmalısınız. Alt kabuk ile yeni süreç arasındaki fark için subshell ve çalışma ortamı yazısına göz atabilirsiniz.

Dört temel bayrak: -s, -u, -q ve -p

Dört temel bayrak: -s, -u, -q ve -p: Verilen seçenekleri açar (set), Verilen seçenekleri kapatır (unset), Çıktı vermez, durumu çıkış koduyla bildirir, Yeniden girdi olarak...

Komutun yazımı shopt [-pqsu] [-o] [optname ...] şeklindedir. Hiç bayrak vermezseniz ya da yalnızca -p verirseniz komut seçenekleri ve durumlarını listeler; isim verirseniz liste o isimlerle sınırlanır. Aşağıdaki tablo bayrakları özetliyor:

BayrakNe yaparÖrnek
-sVerilen seçenekleri açar (set)shopt -s nullglob dotglob
-uVerilen seçenekleri kapatır (unset)shopt -u nocasematch
-qÇıktı vermez, durumu çıkış koduyla bildirirshopt -q globstar && echo açık
-pYeniden girdi olarak kullanılabilir biçimde listelershopt -p extglob
-oİsimleri set -o tablosundan alırshopt -s -o pipefail
bash
#!/usr/bin/env bash
shopt nullglob extglob
# çıktı:
#   nullglob       	off
#   extglob        	off
shopt -s nullglob
shopt -p nullglob extglob
# çıktı:
#   shopt -s nullglob
#   shopt -u extglob
if shopt -q nullglob; then
  echo "nullglob açık"
fi
# çıktı: nullglob açık
shopt -q nullglob extglob
echo "\$? = $?"
# çıktı: $? = 1

Örnekte dikkat edilecek iki nokta var. Birincisi, -p çıktısı shopt -s nullglob gibi doğrudan çalıştırılabilecek satırlardır; bu özelliği aşağıda seçenekleri geri yüklemek için kullanacağız. İkincisi, birden fazla isimle -q kullanıldığında çıkış kodu ancak hepsi açıksa 0 olur. nullglob açık, extglob kapalı olduğu için sonuç 1 çıktı. Aynı kural listeleme için de geçerlidir: kılavuza göre seçenek listelenirken dönüş kodu, adı verilen seçeneklerin tamamı açıksa sıfırdır. Bu yüzden set -e açık bir betikte shopt -p extglob gibi masum görünen bir listeleme satırı, seçenek kapalıysa betiği durdurabilir.

-s veya -u bayrağını isim vermeden kullanırsanız, komut sırasıyla yalnızca açık ya da yalnızca kapalı seçenekleri gösterir. Bir sunucuda hangi ayarların etkin olduğunu hızlıca görmek için shopt -s yazmak yeterlidir; ancak bu liste etkileşimli kabukta ve betikte farklı olabilir, çünkü bazı seçenekler (örneğin expand_aliases) etkileşimli kabukta otomatik açılır.

set -o ile shopt arasındaki fark

set -o tablosundaki seçenekleri shopt ile de yönetebilirsiniz: -o bayrağı, isimlerin set komutunun -o listesinden alınacağını belirtir. Bu, özellikle tek bir sözdizimiyle her iki tabloyu kaydedip geri yüklemek istediğinizde pratiktir. shopt -p -o çıktısının shopt değil set -o veya set +o satırları ürettiğine dikkat edin:

bash
#!/usr/bin/env bash
shopt -s -o pipefail
set -o | grep pipefail
# çıktı: pipefail       	on
shopt -p -o pipefail nounset
# çıktı:
#   set -o pipefail
#   set +o nounset

Kabuk, her iki tablonun güncel durumunu iki salt okunur değişkende tutar. SHELLOPTS, set -o ile açık olan seçenekleri; BASHOPTS ise shopt ile açık olanları iki nokta üst üste ile ayrılmış bir liste olarak içerir. Bu değişkenlerden biri ortamda (environment) varken Bash başlarsa, listedeki her seçeneği başlangıç dosyalarını okumadan önce açar. Yani export BASHOPTS yaparsanız, alt Bash süreçleri de aynı shopt seçenekleriyle başlar. Değişkenler salt okunur olduğu için onlara doğrudan atama yapamazsınız:

bash
#!/usr/bin/env bash
shopt -s extglob
case ":$BASHOPTS:" in
  *:extglob:*) echo "BASHOPTS içinde extglob var" ;;
esac
# çıktı: BASHOPTS içinde extglob var
BASHOPTS=foo
# çıktı: ornek.sh: line 7: BASHOPTS: readonly variable

Bir seçeneği Bash'i başlatırken açmak da mümkündür: bash -O globstar betik.sh komutu betiği globstar açık olarak çalıştırır, +O ise kapatır. Seçenek adı vermeden bash -O yazarsanız Bash tüm shopt seçeneklerini listeler. Yine de betiğin davranışını çağıran kişinin hatırlamasına bırakmak yerine seçeneği betiğin içinde açmak daha öngörülebilir bir yoldur.

Betiklerde en çok kullanılan seçenekler

Betiklerde en çok kullanılan seçenekler: nullglob: eşleşmeyen desen kaybolsun, failglob: eşleşmeyen desen hata versin, dotglob ve globskipdots: noktalı dosyalar, globstar: ** ile...

nullglob: eşleşmeyen desen kaybolsun

Varsayılan davranışta hiçbir dosyayla eşleşmeyen bir joker desen olduğu gibi bırakılır. Bu yüzden for f in *.log döngüsü, klasörde hiç .log dosyası yoksa bir kez, f değişkeni *.log metnini taşıyarak döner. Döngünün içinde bu dosyayı silmeye ya da arşivlemeye çalışan bir komut "dosya bulunamadı" hatası verir. nullglob açıldığında eşleşmeyen desen tamamen kaldırılır, döngü hiç çalışmaz ve diziye okunan dosya sayısı doğru biçimde 0 olur:

bash
#!/usr/bin/env bash
mkdir -p ./klasor && cd ./klasor || exit 1
for f in *.log; do
  echo "işleniyor: [$f]"
done
# çıktı: işleniyor: [*.log]
shopt -s nullglob
for f in *.log; do
  echo "işleniyor: [$f]"
done
echo "döngü bitti"
# çıktı: döngü bitti
dosyalar=( *.log )
echo "bulunan: ${#dosyalar[@]}"
# çıktı: bulunan: 0

nullglob her yerde aynı ölçüde zararsız değildir. Argümansız çalıştırıldığında farklı davranan komutlarda dikkatli olun: nullglob açıkken eşleşmeyen bir desen komuta hiç argüman bırakmaz ve komut, argümansız çağrıldığında ne yapıyorsa onu yapar; argüman yoksa standart girdiyi okuyan bir komut, örneğin, beklemeye başlar. Bu yüzden seçeneği genelde yalnızca ihtiyaç duyulan bölüm için açıp sonra kapatmak iyi bir alışkanlıktır.

failglob: eşleşmeyen desen hata versin

failglob, nullglob'un tersine sessiz kalmak yerine hata üretir: eşleşme yoksa Bash bir hata mesajı yazar ve o komutu çalıştırmaz. Denediğimiz Bash 5.2.37'de, POSIX modu kapalıyken betik bir sonraki satırdan devam etti; yani failglob tek başına betiği durdurmaz, yalnızca hatalı komutu atlar:

bash
#!/usr/bin/env bash
mkdir -p ./klasor && cd ./klasor || exit 1
shopt -s failglob
echo *.log
echo "bu satır çalışır mı?"
# çıktı:
#   ornek.sh: line 4: no match: *.log
#   bu satır çalışır mı?

dotglob ve globskipdots: noktalı dosyalar

Adı nokta ile başlayan dosyalar (.env, .htaccess gibi) varsayılan olarak * deseniyle eşleşmez. dotglob açıldığında bu dosyalar da sonuca girer. . ve .. girdileri ise hiçbir zaman bu yolla eşleşmez; varsayılan olarak açık gelen globskipdots seçeneği, desen nokta ile başlasa bile bu iki ismi dışarıda tutar. Bu sayede .* gibi bir desenle yanlışlıkla üst dizine taşmazsınız.

bash
#!/usr/bin/env bash
mkdir -p ./nokta && cd ./nokta || exit 1
: > .gizli; : > acik.txt
echo *
# çıktı: acik.txt
shopt -s dotglob
echo *
# çıktı: .gizli acik.txt

globstar: ** ile alt dizinler

globstar kapalıyken ** sıradan bir * gibi davranır ve yalnızca bir dizin seviyesi eşleşir. Seçenek açıldığında ** sıfır veya daha fazla dizin seviyesini kapsar; arkasından / gelirse yalnızca dizinler eşleşir. Harici bir arama aracına ihtiyaç duymadan tüm alt klasörlerdeki dosyaları dolaşmak için kullanışlıdır:

bash
#!/usr/bin/env bash
mkdir -p ./agac/a/b && cd ./agac || exit 1
: > kok.txt; : > a/bir.txt; : > a/b/iki.txt
echo **/*.txt
# çıktı: a/bir.txt
shopt -s globstar
echo **/*.txt
# çıktı: a/b/iki.txt a/bir.txt kok.txt
echo **/
# çıktı: a/ a/b/

Sonuçlar, joker genişletmenin her zamanki gibi sıralanmış bir listesidir; sıralama geçerli yerel ayara (locale) bağlıdır. İlk echo satırında globstar kapalı olduğu için **/*.txt yalnızca bir alt dizin seviyesindeki a/bir.txt dosyasını buldu; seçenek açıldıktan sonra kök dizindeki ve iki seviye aşağıdaki dosyalar da listeye girdi. **/ deseni ise yalnızca dizinleri döndürdü.

nocaseglob ve nocasematch: büyük/küçük harf

nocaseglob dosya adı genişletmesini, nocasematch ise case ve [[ ]] içindeki desen eşleştirmesini büyük/küçük harfe duyarsız yapar. Kılavuza göre nocasematch, desen değiştirme genişletmelerini (${v/desen/yeni}) ve programlanabilir tamamlamadaki filtrelemeyi de etkiler. Kullanıcının yazdığı "EVET", "Evet" veya "evet" cevaplarını tek bir dalla yakalamak için idealdir:

bash
#!/usr/bin/env bash
cevap="EVET"
shopt -s nocasematch
case "$cevap" in
  evet) echo "onaylandı" ;;
  *)    echo "anlaşılmadı" ;;
esac
# çıktı: onaylandı
[[ "Merhaba" == merhaba ]] && echo "[[ ]] da eşleşti"
# çıktı: [[ ]] da eşleşti

Örnek LC_ALL=C.UTF-8 ile çalıştırıldı. Bu bir Türkçe yerel ayar olmadığı için İ/i ve I/ı eşleşmeleri Türkçe kurallarına göre yapılmaz; Türkçe metinlerle büyük/küçük harf karşılaştırması yaparken bu sınırı aklınızda tutun. Desenlerin yazımı için case esac yazısına bakabilirsiniz.

extglob: genişletilmiş desenler

?(...), *(...), +(...), @(...) ve !(...) desenleri ancak extglob açıkken tanınır. Kılavuz burada önemli bir uyarı yapar: bu seçenek ayrıştırıcının (parser) davranışını değiştirir, çünkü parantezler normalde sözdizimsel anlam taşır. Bu nedenle extglob, desenleri içeren fonksiyonlar ve komut ikameleri ayrıştırılmadan önce açılmış olmalıdır; aynı satırda ya da aynı fonksiyon gövdesinde açmak yetmeyebilir. [[ ]] içindeki == ve != karşılaştırmaları ise kılavuza göre zaten extglob açıkmış gibi çalışır. Ayrıntılar extglob rehberinde.

lastpipe: boru hattının son komutu mevcut kabukta

Bir boru hattındaki (pipeline) komutlar normalde alt kabuklarda çalışır; bu yüzden ... | while read döngüsünde değiştirdiğiniz değişken döngüden sonra eski değerine döner. lastpipe açıkken ve iş denetimi (job control) etkin değilken Bash, arka plana atılmamış bir boru hattının son komutunu mevcut kabukta çalıştırır:

bash
#!/usr/bin/env bash
sayi=0
printf 'a\nb\nc\n' | while read -r satir; do sayi=$((sayi + 1)); done
echo "lastpipe kapalı: $sayi"
# çıktı: lastpipe kapalı: 0
shopt -s lastpipe
printf 'a\nb\nc\n' | while read -r satir; do sayi=$((sayi + 1)); done
echo "lastpipe açık: $sayi"
# çıktı: lastpipe açık: 3

Etkileşimli kabukta iş denetimi genellikle açık olduğu için bu seçenek orada beklediğiniz etkiyi göstermeyebilir; betiklerde ise varsayılan olarak iş denetimi kapalıdır. Aynı sorunun başka çözümleri için pipe ve PIPESTATUS yazısına ve process substitution rehberine bakabilirsiniz.

expand_aliases ve shift_verbose

Etkileşimli olmayan kabuklarda, yani betiklerde, alias'lar genişletilmez. Bir betikte alias kullanmak zorundaysanız expand_aliases seçeneğini açmanız gerekir; yine de kılavuz hemen her amaç için fonksiyonların alias'lardan daha uygun olduğunu belirtir. Alias tanımının kullanıldığı satırdan önceki bir satırda yapılması gerektiğini de unutmayın, çünkü Bash bir satırı tamamen okuduktan sonra çalıştırır. Ayrıntılar için alias tanımlama yazısına göz atabilirsiniz.

bash
#!/usr/bin/env bash
alias selam='echo merhaba'
selam 2>/dev/null || echo "alias genişlemedi"
# çıktı: alias genişlemedi
shopt -s expand_aliases
selam
# çıktı: merhaba

shift_verbose ise shift komutuna, mevcut konumsal parametre sayısından büyük bir değer verildiğinde hata mesajı yazdırır. Kapalıyken shift yalnızca 1 döner ve hiçbir şey söylemez; bu da argüman ayrıştırma hatalarını gözden kaçırmanıza neden olabilir. Konumsal parametreler için betik argümanları yazısına bakın.

bash
#!/usr/bin/env bash
set -- a b
shift 5
echo "sessiz: \$? = $?"
# çıktı: sessiz: $? = 1
shopt -s shift_verbose
shift 5
# çıktı: ornek.sh: line 7: shift: 5: shift count out of range

Etkileşimli kabuk için olanlar

Bazı seçenekler yalnızca etkileşimli kabukta anlam taşır ve genellikle ~/.bashrc içine yazılır. histappend kabuk kapanırken geçmiş dosyasının üzerine yazmak yerine sonuna ekler; cmdhist (varsayılan açık) çok satırlı komutları tek bir geçmiş girdisinde tutar; checkwinsize (varsayılan açık) harici komutlardan sonra LINES ve COLUMNS değerlerini günceller; autocd bir dizin adını tek başına yazdığınızda oraya cd yapar; cdspell ise cd argümanındaki küçük yazım hatalarını düzeltmeye çalışır. Geçmiş ayarları için history rehberine, başlangıç dosyaları için bashrc ve bash_profile yazısına bakın. login_shell ve restricted_shell ise kabuğun kendisinin ayarladığı, sizin değiştiremediğiniz bilgi seçenekleridir.

Seçeneği geçici değiştirip geri almak

Bir kütüphane fonksiyonunun içinde nullglob açıp öylece bırakırsanız, fonksiyonu çağıran betiğin geri kalanı da beklemediği bir davranışla karşılaşır. Güvenli yol, seçeneğin mevcut durumunu shopt -p ile bir değişkene kaydetmek, işinizi yapmak ve kaydedilen satırı eval ile çalıştırarak eski duruma dönmektir:

bash
#!/usr/bin/env bash
eski=$(shopt -p nullglob)
echo "kaydedildi: $eski"
# çıktı: kaydedildi: shopt -u nullglob
shopt -s nullglob
# ... nullglob gereken iş ...
eval "$eski"
shopt nullglob
# çıktı: nullglob       	off

Burada eval kullanmak güvenlidir, çünkü çalıştırılan metin dışarıdan gelen bir girdi değil, kabuğun kendi ürettiği bir shopt satırıdır. eval komutunun riskleri için eval ve dolaylı değişken yazısına bakın. Bir başka yol da işi parantez içinde, yani bir alt kabukta yapmaktır: ( shopt -s nullglob; ... ). Alt kabukta yaptığınız değişiklikler ana kabuğa yansımaz; ancak aynı nedenle alt kabukta atadığınız değişkenler de dışarı çıkmaz.

Geçersiz seçenek adı

Yazım hatası yaptığınızda shopt bir hata mesajı yazar ve 1 döner. Kılavuz, ayarlama sırasında dönüş kodunun ancak geçersiz bir isim verildiğinde sıfırdan farklı olduğunu belirtir:

bash
#!/usr/bin/env bash
shopt -s nulglob
echo "\$? = $?"
# çıktı:
#   ornek.sh: line 2: shopt: nulglob: invalid shell option name
#   $? = 1

Bu davranış aynı zamanda bir özelliğin var olup olmadığını anlamanın da yoludur. Kılavuzdaki bazı seçenekler (örneğin array_expand_once ve bash_source_fullpath) Bash 5.3 belgelerinde yer alıyor, ama denediğimiz 5.2.37'de bulunmuyor. Sürüm numarasına bakmak yerine shopt -p seçenek &>/dev/null ile seçeneğin varlığını sınamak daha sağlamdır; bu yöntemi Bash sürümü ve uyumluluk modu yazısında örnekliyoruz.

Sık yapılan hatalar

Sık yapılan hatalar: Seçeneği komut satırında açıp betikte beklemek, set ile shopt'u karıştırmak, extglob'u desenle aynı yapıda açmak, Kütüphane kodunda seçeneği açık bırakmak
  • Seçeneği komut satırında açıp betikte beklemek: Yeni başlatılan bash süreci varsayılan ayarlarla açılır; seçeneği betiğin içinde açın.
  • set ile shopt'u karıştırmak: set -o nullglob geçersizdir, shopt -s pipefail da öyle. Doğru yazım shopt -s nullglob ve set -o pipefail (ya da shopt -s -o pipefail).
  • extglob'u desenle aynı yapıda açmak: Seçenek, deseni içeren fonksiyon veya satır ayrıştırılmadan önce açılmalıdır.
  • Kütüphane kodunda seçeneği açık bırakmak: shopt -p ile kaydedin, işiniz bitince geri yükleyin.
  • set -e açıkken listelemek: Kapalı bir seçeneği listelemek 1 döndürür ve betiği durdurabilir; durumu öğrenmek için if shopt -q ... kullanın.

Sık Sorulan Sorular

Hangi shopt seçeneklerinin açık olduğunu nasıl görürüm?

shopt -s yalnızca açık olanları, shopt -u yalnızca kapalı olanları listeler. Tümünü görmek için bayraksız shopt, yeniden kullanılabilir biçim için shopt -p yazın. Aynı bilgi BASHOPTS değişkeninde de bulunur.

shopt ayarlarını kalıcı yapmak mümkün mü?

shopt bir kabuk oturumu boyunca geçerlidir. Etkileşimli kabukta her açılışta etkin olmasını istediğiniz seçenekleri ~/.bashrc dosyasına yazarsınız; betikler için ise seçeneği betiğin başında açmak gerekir, çünkü etkileşimli olmayan kabuklar ~/.bashrc dosyasını okumaz.

nullglob mu failglob mu kullanmalıyım?

Eşleşme olmamasının normal bir durum olduğu yerlerde (örneğin boş olabilecek bir yükleme klasörü) nullglob, eşleşme olmamasının bir hata anlamına geldiği yerlerde failglob uygundur. İkisi aynı anda açılırsa eşleşmeyen desen hata verir.

shopt -o ile set -o aynı şey mi?

Evet, shopt -s -o pipefail ile set -o pipefail aynı seçeneği açar. Fark yalnızca yazımdadır; shopt -p -o çıktısı set -o satırları üretir.

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 joker karakterler · extglob desenleri · set -euo pipefail · pipe ve pipefail · bashrc ve bash_profile farkı