Bash'te extglob, shopt -s extglob ile açılan ve normal joker karakterlere beş yeni desen operatörü ekleyen bir seçenektir. ?(desen) sıfır veya bir kez, *(desen) sıfır veya daha fazla, +(desen) bir veya daha fazla, @(desen) tam olarak bir kez eşleşir; !(desen) ise verilen desenlerin dışındaki her şeyle eşleşir. Parantez içine | ile ayrılmış birden çok desen yazabilirsiniz: *.@(jpg|png) JPG veya PNG uzantılı dosyaları, !(*.conf) uzantısı .conf olmayan her şeyi seçer.
Bu yazıda beş operatörü dosya adlarında, case komutunda, [[ ]] karşılaştırmalarında ve parametre genişletmelerinde çalışan örneklerle gösteriyoruz. En çok kafa karıştıran nokta olan "shopt -s extglob yazdım ama syntax error alıyorum" sorununu ayrı bir başlıkta açıklıyoruz. Temel joker karakterleri henüz bilmiyorsanız önce Bash glob yazısını okuyun; serinin tamamı Bash öğrenme rehberinde.
extglob nedir, neden ayrı bir seçenek?

Normal joker karakterler basit işler için yeterlidir ama bazı soruları yanıtlayamaz: "bu uzantılardan herhangi biri", "bu desen hariç her şey" veya "bir ya da daha fazla rakam" gibi. Genişletilmiş desenler bu boşluğu doldurur ve gördükleri iş bakımından düzenli ifadelerdeki gruplama ve tekrar operatörlerine benzer. Kılavuzun Bourne kabuğu farklarını listeleyen ekinde bu desenler, Bash'in Bourne kabuğuna eklediği özellikler arasında geçer; bu yüzden sh betiklerinde kullanılamazlar.
Ayrı bir seçenek olmasının nedeni sözdizimidir. Kabukta parantez normalde alt kabuk ve fonksiyon tanımı gibi yapılarda kullanılan bir operatördür. @(a|b) gibi bir ifadeyi desen olarak tanıyabilmesi için Bash'in ayrıştırıcısının (parser) davranışı değişmelidir. Kılavuz bunu açıkça belirtir: extglob ayrıştırıcının davranışını değiştirir. Seçenek kapalıyken bu yazımlar sözdizimi hatası verir.
Beş operatör: ?() *() +() @() !()
Her operatör parantez içinde bir desen listesi alır. Liste | ile ayrılmış bir veya daha fazla desenden oluşur ve her desen içinde normal joker karakterler ile başka genişletilmiş desenler kullanılabilir. Aşağıdaki örnek operatörlerin farkını [[ ]] içinde, dosya sistemine dokunmadan gösteriyor.
shopt -s extglob
dene() { [[ $1 == $2 ]] && echo "$2 '$1' ile eşleşti" || echo "$2 '$1' ile eşleşmedi"; }
dene b '?(a)b' # a sıfır veya bir kez
dene ab '?(a)b'
dene aab '?(a)b'
# çıktı:
# ?(a)b 'b' ile eşleşti
# ?(a)b 'ab' ile eşleşti
# ?(a)b 'aab' ile eşleşmedi
dene "" '*(a)' # sıfır veya daha fazla: boş metin de olur
dene "" '+(a)' # en az bir kez gerekir
dene aaa '+(a)'
# çıktı:
# *(a) '' ile eşleşti
# +(a) '' ile eşleşmedi
# +(a) 'aaa' ile eşleşti
dene png '@(jpg|png)' # listeden tam olarak biri
dene gif '!(jpg|png)' # listedekiler dışında herhangi bir şey
# çıktı:
# @(jpg|png) 'png' ile eşleşti
# !(jpg|png) 'gif' ile eşleştiBu örnekte deseni tek tırnakla fonksiyona argüman olarak veriyor, fonksiyonun içinde [[ $1 == $2 ]] ile sağ tarafı tırnaksız kullanıyoruz. Sağ taraf tırnaksız olduğu için değişkenin değeri desen olarak yorumlanır. "$2" yazsaydık kılavuzun kuralı gereği desen düz metin olarak karşılaştırılırdı. Fonksiyonlar için fonksiyon yazısına bakabilirsiniz.
En sık sorun: shopt -s extglob yazdım ama syntax error alıyorum
Bash bir satırı, bir fonksiyon tanımını veya if, case gibi bileşik bir komutu çalıştırmadan önce bütünüyle ayrıştırır. shopt -s extglob ise çalışma anında etkili olur. Kılavuzun uyarısı bu yüzden önemlidir: genişletilmiş desen içeren yapılar ayrıştırılmadan önce extglob açık olmalıdır; buna fonksiyon tanımları ve komut ikameleri de dahildir. Seçeneği desenle aynı satırda veya desenin kullanıldığı fonksiyonun içinde açmak işe yaramaz.
shopt -s extglob; echo @(a|b)
echo "bu satır çalışmaz"
# çıktı:
# ornek.sh: line 1: syntax error near unexpected token `('
# ornek.sh: line 1: `shopt -s extglob; echo @(a|b)'Hata satırı ayrıştırılırken oluştuğu için shopt komutu hiç çalışmadı. Etkileşimsiz bir kabukta sözdizimi hatası betiği sonlandırdığından ikinci satır da çalışmadı. Aynı sorun, seçeneği fonksiyonun içinde açtığınızda da görülür: Bash fonksiyonun gövdesini tanım anında ayrıştırır, shopt ise ancak fonksiyon çağrıldığında çalışır.
kontrol() { shopt -s extglob; case $1 in +([0-9])) echo sayı ;; esac; }
# çıktı:
# ornek.sh: line 1: syntax error near unexpected token `('
# ornek.sh: line 1: `kontrol() { shopt -s extglob; case $1 in +([0-9])) echo sayı ;; esac; }'Çözüm basittir: shopt -s extglob komutunu betiğin en başına, kendi satırına ve deseni kullanan her yapıdan önceye yazın. Bu satır çalıştıktan sonra ayrıştırılan her şey genişletilmiş desenleri tanır. Betiğiniz birden fazla dosyadan oluşuyorsa ve desenleri source ile yüklenen bir dosyada kullanıyorsanız, seçeneği source satırından önce açmanız yeterlidir; çünkü yüklenen dosya da ancak okunduğu anda ayrıştırılır.
shopt -s extglob
kontrol() {
case $1 in
+([0-9])) echo "$1: sayı" ;;
*) echo "$1: sayı değil" ;;
esac
}
kontrol 2024
kontrol 20a4
# çıktı:
# 2024: sayı
# 20a4: sayı değilİlginç bir ayrıntı: aynı fonksiyonu case yerine [[ $1 == +([0-9]) ]] ile yazsaydık, seçenek fonksiyonun içinde açılsa bile sözdizimi hatası almazdık. Bunun nedeni bir sonraki başlıkta anlatılan [[ ]] istisnasıdır. Bu istisna hatayı gizleyebilir: deseni bir gün case içine taşıdığınızda betik birden çalışmaz hale gelir.
Kılavuzun uyumluluk modu bölümü bu konuda bir sürüm ayrıntısı da verir: uyumluluk seviyesi 51 veya daha düşük ayarlandığında komut ikameleri, içlerinde extglob deseni varsa ayrıştırma hatası vermesin diye extglob açıkmış gibi ayrıştırılır; ancak seçenek çalışma anında hâlâ kapalıysa genişletme sırasında hata alınır. Varsayılan davranışa güvenmek yerine seçeneği her zaman önceden açmak en sağlam yoldur. Uyumluluk seviyeleri sürüm ve uyumluluk modu yazısında anlatılıyor.
[[ ]] içinde extglob kapalıyken de çalışır
![[[ ]] içinde extglob kapalıyken de çalışır](/assets/blog/bash-extglob-genisletilmis-desenler-b4.webp)
Kılavuzun koşullu yapılar bölümüne göre [[ ]] içinde == ve != operatörlerinin sağındaki metin, extglob seçeneği açıkmış gibi eşleştirilir. Bash 5.2.37 ile denediğimizde de seçenek kapalıyken [[ ]] genişletilmiş desenleri kabul etti. case komutu ise bu istisnadan yararlanmaz; orada seçenek açık olmadan aynı desen sözdizimi hatası verir.
# extglob kapalı
dosya=resim.png
[[ $dosya == *.@(jpg|png) ]] && echo "[[ ]] içinde çalıştı"
# çıktı: [[ ]] içinde çalıştı
shopt -q extglob || echo "extglob seçeneği kapalı"
# çıktı: extglob seçeneği kapalı
shopt -p extglob # yeniden kullanılabilir biçim
# çıktı: shopt -u extglobYine de betiklerinizde [[ ]] içinde genişletilmiş desen kullanıyorsanız seçeneği başta açık açık etkinleştirmek okuyan kişiye niyetinizi gösterir ve aynı deseni ileride case veya bir parametre genişletmesine taşıdığınızda sürprizle karşılaşmazsınız. [[ ]] ile [ ] arasındaki diğer farklar test ve [[ ]] yazısında.
case komutunda genişletilmiş desenler
case komutu her deseni sırayla dener ve ilk eşleşen dalı çalıştırır. Genişletilmiş desenler bir dalda çok sayıda alternatifi kısa ve okunur biçimde yazmanızı sağlar. Normal case zaten a|b) biçiminde alternatifleri destekler, ama bu alternatifler tüm desen düzeyindedir; extglob ise alternatifi desenin bir parçasına uygulamanıza izin verir.
shopt -s extglob
tur() {
case $1 in
*.@(jpg|jpeg|png|gif)) echo "$1: görsel" ;;
*.tar.@(gz|bz2|xz)) echo "$1: arşiv" ;;
?([-+])+([0-9])) echo "$1: tamsayı" ;;
*) echo "$1: bilinmiyor" ;;
esac
}
for x in tatil.jpeg yedek.tar.xz -42 +7 notlar.txt; do tur "$x"; done
# çıktı:
# tatil.jpeg: görsel
# yedek.tar.xz: arşiv
# -42: tamsayı
# +7: tamsayı
# notlar.txt: bilinmiyor?([-+])+([0-9]) deseni "isteğe bağlı bir işaret ve ardından en az bir rakam" demektir ve kullanıcı girdisini doğrulamak için sık kullanılır. Ondalık sayıları kabul etmez; 1.5 bu desene uymaz. case sözdiziminin ayrıntıları, ;& ve ;;& sonlandırıcıları case esac yazısında anlatılıyor.
Dosya adlarında extglob: !(desen) ile hariç tutma
Genişletilmiş desenler dosya adı genişletmesinde de çalışır. En kullanışlı olanı !(desen)'dir: belirli dosyalar dışındaki her şeyi seçer. Bir klasörde yalnızca yapılandırma dosyalarını bırakıp diğerlerini taşımak gibi işler için idealdir. Normal glob kurallarının hepsi geçerliliğini korur: gizli dosyalar dotglob açık değilse seçilmez ve sonuçlar alfabetik sıralanır.
shopt -s extglob
touch a.txt b.log c.conf d.conf .e.conf
echo !(*.conf) # .conf olmayan her şey (betik dahil)
# çıktı: a.txt b.log ornek.sh
echo *.@(txt|log) # .txt veya .log
# çıktı: a.txt b.log
echo !(*.conf|*.sh) # birden çok desen hariç
# çıktı: a.txt b.log
shopt -s dotglob
echo !(*.txt|*.log|*.sh) # dotglob ile gizli dosyalar da test edilir
# çıktı: .e.conf c.conf d.conf
shopt -u dotglobKılavuz GLOBIGNORE değişkenindeki desen eşleşmesinin de extglob ayarına uyduğunu belirtir; yani seçenek açıkken GLOBIGNORE='+(c|d).conf' gibi bir değer kullanılabilir. GLOBIGNORE ve dotglob ilişkisi glob yazısında anlatılıyor.
Parametre genişletmesinde extglob: boşluk kırpma ve toplu değiştirme
Desen alan her parametre genişletmesi, yani #, ##, %, %% ve / operatörleri, extglob açıkken genişletilmiş desenleri de kabul eder. En bilinen kullanımı, harici bir araç çağırmadan bir metnin başındaki ve sonundaki boşlukları kırpmaktır: +([[:space:]]) "bir veya daha fazla boşluk karakteri" demektir ve ## ile en uzun eşleşme baştan silinir.
shopt -s extglob
metin=" merhaba dünya "
kirp=${metin##+([[:space:]])} # baştaki boşluklar
kirp=${kirp%%+([[:space:]])} # sondaki boşluklar
echo "[$kirp]"
# çıktı: [merhaba dünya]
yol="eski-eski-yeni"
echo "${yol//@(eski|yeni)/X}" # her iki kelimeyi de değiştir
# çıktı: X-X-X
echo "${yol/#+(eski-)/}" # baştaki tekrarlanan öneki sil
# çıktı: yeniBu genişletmelerin genel kuralları, yani kısa ve uzun eşleşme farkı ile /# ve /% bağlantıları, string işlemleri yazısında ayrıntılı anlatılıyor. Kılavuz bir performans uyarısı da yapar: uzun metinlere karşı çok sayıda alternatif içeren karmaşık genişletilmiş desenler yavaş çalışabilir. Bu durumda metni daha kısa parçalara bölmek veya değerleri tek bir uzun metin yerine bir dizide tutmak daha hızlı olabilir.
!(desen) tuzakları
Tersleme operatörü sezgiyle uyuşmayan sonuçlar verebilir, çünkü "verilen desenlerden biriyle eşleşmeyen herhangi bir metin" anlamına gelir ve bu metin boş da olabilir. !(foo)* deseni foo.txt ile eşleşir: !(foo) kısmı boş metinle veya f ile eşleşir, kalan kısmı * alır. Bir şeyi hariç tutmak istiyorsanız tersleme operatörünün kapsamının bütün adı kapsadığından emin olun.
shopt -s extglob
[[ foo.txt == !(foo)* ]] && echo "!(foo)* yine de foo.txt ile eşleşti"
# çıktı: !(foo)* yine de foo.txt ile eşleşti
[[ foo.txt == !(foo*) ]] || echo "!(foo*) foo.txt dosyasını dışarıda bıraktı"
# çıktı: !(foo*) foo.txt dosyasını dışarıda bıraktı
[[ rapor.txt == !(*.txt) ]] || echo "!(*.txt) rapor.txt ile eşleşmedi"
# çıktı: !(*.txt) rapor.txt ile eşleşmediKural olarak !( ) operatörünü desenin tamamını saracak biçimde yazın: !(*.txt) doğru, !(*).txt ise niyetinizi ifade etmez. Emin değilseniz önce [[ ]] içinde birkaç örnek metinle deneyin.
Operatör tablosu ve regex karşılıkları

| Desen | Anlamı | Yaklaşık ERE karşılığı |
|---|---|---|
| ?(a|b) | a veya b, sıfır ya da bir kez | (a|b)? |
| *(a|b) | a veya b, sıfır ya da daha fazla kez | (a|b)* |
| +(a|b) | a veya b, bir ya da daha fazla kez | (a|b)+ |
| @(a|b) | a veya b, tam olarak bir kez | (a|b) |
| !(a|b) | a ve b dışında herhangi bir metin | doğrudan karşılığı yok |
Tablodaki regex karşılıkları yalnızca fikir vermek içindir; desenler her zaman metnin tamamıyla eşleşirken =~ ile kullanılan düzenli ifadeler metnin herhangi bir yerinde eşleşebilir. Genişletilmiş desenlerin yetmediği durumlarda Bash regex ve BASH_REMATCH yazısına geçebilirsiniz.
Sık yapılan hatalar
- Seçeneği desenle aynı satırda açmak: satır ayrıştırılırken seçenek henüz kapalıdır;
shopt -s extglobayrı bir satırda olmalı. - Seçeneği fonksiyonun içinde açmak: fonksiyon gövdesi tanım anında ayrıştırılır; seçeneği tanımdan önce açın.
[[ ]]içinde çalıştı diyecaseiçinde de çalışacağını sanmak:caseseçeneğin açık olmasını ister.- Deseni tırnaklamak:
[[ $x == "@(a|b)" ]]düz metin karşılaştırmasıdır. !(desen)kapsamını yanlış kurmak:!(foo)*neredeyse her şeyle eşleşir.- Silmeden önce denememek: dosya adı desenlerini önce
echoile kontrol edin.
Sık Sorulan Sorular
extglob varsayılan olarak açık mı?
Kılavuza göre shopt seçenekleri, aksi belirtilmedikçe kapalı başlar ve extglob için böyle bir not yoktur. Bash 5.2.37 ile çalıştırdığımız betiklerde de kapalıydı. Bazı dağıtımlar etkileşimli kabuk için başlangıç dosyalarında açıyor olabilir; kendi oturumunuzda shopt extglob ile kontrol edin. Betiklerde bu duruma güvenmeyin ve seçeneği kendiniz açın.
extglob açmak normal joker karakterleri etkiler mi?
Hayır. *, ? ve [ ] aynı biçimde çalışmaya devam eder. Yalnızca ?(, *(, +(, @( ve !( dizileri yeni anlam kazanır.
Genişletilmiş desenleri iç içe kullanabilir miyim?
Evet, bir desen listesindeki her desen başka genişletilmiş desenler içerebilir: @(+([0-9]).+([0-9])|+([0-9])) hem 12 hem de 12.5 biçimindeki sayılarla eşleşir. Okunabilirlik azaldığında regex düşünmeye değer.
Seçeneği betik bitince kapatmam gerekir mi?
Hayır. Betik kendi kabuk sürecinde çalışır ve seçenek o süreçle birlikte biter. Ancak source ile başka bir betiğe yüklenen bir kütüphane dosyası yazıyorsanız, çağıranın ayarlarını değiştirmemek için seçeneğin önceki durumunu kaydedip geri yüklemeyi düşünün; shopt -p extglob çıktısı tam olarak bunu yapan komutu verir. Ayrıntılar için shopt yazısına ve source yazısına bakabilirsiniz.
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 · case esac · test ve [[ ]] · String işlemleri · shopt seçenekleri