Bash'te bir metnin düzenli ifadeyle (regex) eşleşip eşleşmediğini [[ metin =~ desen ]] ile sınarsınız. Desen POSIX genişletilmiş düzenli ifadesi (ERE) olarak yorumlanır; eşleşme varsa durum 0, yoksa 1, desen hatalıysa 2 olur. Eşleşen kısım BASH_REMATCH dizisine yazılır: ${BASH_REMATCH[0]} desenin tamamına uyan bölümü, ${BASH_REMATCH[1]} ve sonrası parantezli grupları tutar. Desenin tırnaklanan kısmı düz metin olarak aranır; bu yüzden deseni bir değişkende tutup [[ $metin =~ $desen ]] biçiminde tırnaksız kullanmak en az sürprizli yoldur.
Bu yazıda GNU Bash kılavuzunun Conditional Constructs bölümündeki =~ anlatımını ve BASH_REMATCH değişkenini çalıştırılmış örneklerle ele alıyoruz: kısmi ve tam eşleşme, grupları yakalama, tırnaklamanın etkisi, ters eğik çizgi tuzakları, tüm eşleşmeleri bir döngüyle bulma ve büyük/küçük harf duyarsız arama. Düzenli ifadelerin genel sözdizimi (karakter sınıfları, nicelik belirteçleri, gruplar) bu yazının konusu değil; onlar için regex başlangıç rehberine bakabilirsiniz.
=~ operatörü ve dönüş değerleri

=~ yalnızca [[ ... ]] bileşik komutunun içinde vardır. Kılavuza göre sağ taraftaki dizge POSIX genişletilmiş düzenli ifadesi olarak, sistemin regcomp ve regexec arayüzleriyle eşleştirilir. Bu ayrıntı önemlidir: regex motoru Bash'in kendisi değil, sistemin C kütüphanesidir ve kullanılabilecek sözdizimi ERE ile sınırlıdır. Perl tarzı kısaltmalar ve ileri bakış gibi özellikler ERE'de tanımlı değildir.
Üç olası dönüş değeri vardır: eşleşme varsa 0, yoksa 1, düzenli ifade sözdizimsel olarak hatalıysa 2. Hatalı desenin 1 değil 2 döndürmesi, "eşleşmedi" ile "desen bozuk" durumlarını ayırt etmenizi sağlar. if yalnızca sıfıra bakar; ikisini ayırmak istiyorsanız durumu bir değişkene kaydedip kontrol edin.
desen='a(b'
[[ abc =~ $desen ]]
echo "durum: $?"
# çıktı: durum: 2Kapanmamış parantez içeren bu desen derlenemediği için durum 2 oldu. Deseni kullanıcıdan veya bir yapılandırma dosyasından alıyorsanız bu değeri kontrol etmek, bozuk bir desenin sessizce "eşleşmedi" diye yorumlanmasını önler.
Kısmi eşleşme ve çapalar
Kılavuz, desenin metnin herhangi bir yerine uyması durumunda eşleşmenin başarılı sayıldığını vurgular. [[ $metin =~ [0-9]+ ]] metnin içinde bir yerde rakam varsa doğrudur. Desenin metnin tamamına uymasını istiyorsanız başına ^, sonuna $ çapası koyarsınız. Glob desenlerinden gelen biri için bu fark şaşırtıcı olabilir: [[ $f == *.txt ]] glob eşleşmesi metnin tamamını ister, =~ ise istemez.
[[ "abc123def" =~ [0-9]+ ]] && echo "bulunan: ${BASH_REMATCH[0]}"
# çıktı: bulunan: 123
[[ "abc123def" =~ ^[0-9]+$ ]]
echo "tam eşleşme: $?"
# çıktı: tam eşleşme: 1Çapa unutmak, girdi doğrulamada en sık yapılan hatadır. Bir port numarasının yalnızca rakamlardan oluştuğunu kontrol etmek isterken [[ $port =~ [0-9]+ ]] yazarsanız 80abc gibi bir değer de geçer, çünkü içinde rakam vardır. Doğrulama yaparken desenin başına ve sonuna çapa koymayı alışkanlık haline getirin; sayısal aralık kontrolünü ise eşleşmeden sonra aritmetik karşılaştırmayla yapın. Aritmetik karşılaştırma için aritmetik işlemler yazısına bakabilirsiniz.
port="80abc"
[[ $port =~ [0-9]+ ]] && echo "çapasız desen: geçerli sanıldı"
[[ $port =~ ^[0-9]+$ ]] || echo "çapalı desen: reddedildi"
# çıktı:
# çapasız desen: geçerli sanıldı
# çapalı desen: reddedildiBASH_REMATCH ile grupları yakalamak
Eşleşme başarılı olduğunda Bash, BASH_REMATCH adlı diziyi doldurur. 0 numaralı eleman desenin tamamına uyan metin parçasıdır; 1, 2 ve sonraki elemanlar desendeki parantezli alt ifadelere sırayla karşılık gelir. Parantezleri soldan sağa, açılış parantezlerinin sırasına göre sayarsınız. Aşağıdaki örnek bir tarih metnini yıl, ay ve gün parçalarına ayırıyor.
tarih="2026-11-07"
if [[ $tarih =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})$ ]]; then
echo "tamamı: ${BASH_REMATCH[0]}"
echo "yıl: ${BASH_REMATCH[1]} ay: ${BASH_REMATCH[2]} gün: ${BASH_REMATCH[3]}"
fi
# çıktı:
# tamamı: 2026-11-07
# yıl: 2026 ay: 11 gün: 07Kılavuz önemli bir uyarı da yapar: Bash BASH_REMATCH dizisini genel (global) kapsamda ayarlar; bu diziyi bir fonksiyon içinde local olarak tanımlamak beklenmedik sonuçlara yol açar. Fonksiyonunuzun sonucu dışarıya taşıması gerekiyorsa dizinin elemanlarını hemen kendi değişkenlerinize kopyalayın. Sonraki her =~ sınaması diziyi yeniden yazar; denememizde başarısız bir eşleşmenin diziyi boşalttığını gördük:
[[ "abc" =~ (b)(c) ]]
echo "eşleşme sonrası: ${#BASH_REMATCH[@]} öğe"
[[ "abc" =~ z ]]
echo "başarısız eşleşme sonrası: ${#BASH_REMATCH[@]} öğe"
# çıktı:
# eşleşme sonrası: 3 öğe
# başarısız eşleşme sonrası: 0 öğeBu yüzden BASH_REMATCH değerlerini yalnızca başarılı bir eşleşmenin hemen ardından, aynı if bloğunun içinde okuyun. Fonksiyonlarda değişken kapsamı için local değişken ve kapsam yazısına, dizilerle çalışmak için dizi kullanımı yazısına bakabilirsiniz.
Tırnaklamanın etkisi
=~ kullanırken yapılan hataların büyük kısmı tırnaklarla ilgilidir. Kural şudur: desenin tırnaklanan herhangi bir kısmı düz metin olarak eşleştirilir, oradaki karakterler özel anlamlarını kaybeder. Bu, istediğinizde işe yarar: noktayı gerçek bir nokta olarak aramak için onu tırnaklayabilirsiniz. Ama çapaları veya grupları tırnak içine alırsanız onlar da düz karaktere dönüşür.
[[ "raporXtxt" =~ r.txt ]]; echo "1 (nokta her karakter): $?"
[[ "raporXtxt" =~ "r.txt" ]]; echo "2 (tırnaklı nokta): $?"
[[ "rapor.txt" =~ "r.txt" ]]; echo "3 (gerçek nokta): $?"
# çıktı:
# 1 (nokta her karakter): 0
# 2 (tırnaklı nokta): 1
# 3 (gerçek nokta): 0Birinci satırda tırnaksız nokta "herhangi bir karakter" anlamına geldiği için rXtxt parçası eşleşti. İkinci satırda tırnaklı nokta yalnızca gerçek bir noktaya uyduğu için eşleşme yok. Üçüncü satırda metinde gerçekten r.txt geçiyor. Kılavuzdaki çapa örneği de aynı kuralı gösteriyor: ^ tırnağın dışındaysa çapadır, içindeyse metinde aranan sıradan bir karakterdir.
satir="initial string ve devamı"
[[ $satir =~ ^"initial string" ]]; echo "çapa tırnak dışında: $?"
[[ $satir =~ "^initial string" ]]; echo "çapa tırnak içinde: $?"
# çıktı:
# çapa tırnak dışında: 0
# çapa tırnak içinde: 1Kılavuz, Bash 3.1 uyumluluk düzeyinde sağ taraftaki tırnaklamanın özel bir etkisi olmadığını, bugünkü davranışın ise bash-3.2 ve sonrasının varsayılanı olduğunu not eder. Çok eski betiklerde farklı bir davranış görürseniz sebebi bu olabilir; uyumluluk düzeyleri Bash sürümü ve uyumluluk modu yazısında anlatılıyor.
Deseni değişkende tutmak

Boşluk, parantez veya | gibi karakterler içeren bir deseni doğrudan [[ ]] içine yazmak, kabuğun kendi sözdizimiyle çakışabilir ve okunması zordur. Kılavuz bunun için deseni bir kabuk değişkeninde tutmayı önerir. Tek tırnakla atanan değişkende hiçbir karakter kabuk tarafından yorumlanmaz; [[ $metin =~ $desen ]] satırında değişken tırnaksız kullanıldığında da içeriği düzenli ifade olarak değerlendirilir.
desen='^([a-z]+) ([0-9]+)$'
for s in "elma 12" "armut" "kiraz 7"; do
if [[ $s =~ $desen ]]; then
echo "$s: ad=${BASH_REMATCH[1]} adet=${BASH_REMATCH[2]}"
else
echo "$s: biçim hatalı"
fi
done
# çıktı:
# elma 12: ad=elma adet=12
# armut: biçim hatalı
# kiraz 7: ad=kiraz adet=7Ters eğik çizgi tuzağı
Ters eğik çizgi hem kabuk hem düzenli ifade için "sonraki karakterin özel anlamını kaldır" demektir; bu yüzden iki katmanın birbirine karışması kolaydır. Kılavuz bunu dört satırlık bir örnekle gösterir ve ilk iki satırın eşleştiğini, son ikisinin eşleşmediğini söyler. Biz aynı satırları çalıştırdık ve bir beşinci satır ekledik:
pattern='\.'
[[ . =~ $pattern ]]; echo "1: $?"
[[ . =~ \. ]]; echo "2: $?"
[[ . =~ "$pattern" ]]; echo "3: $?"
[[ . =~ '\.' ]]; echo "4: $?"
[[ . =~ [.] ]]; echo "5: $?"
# çıktı:
# 1: 0
# 2: 0
# 3: 1
# 4: 1
# 5: 0İlk iki satırda düzenli ifade motoruna \. gider; ters eğik çizgi noktayı gerçek nokta yapar ve eşleşme olur. Üçüncü ve dördüncü satırda ters eğik çizgi tırnak içinde olduğu için kendisi de aranacak bir karakter sayılır; metinde ters eğik çizgi olmadığından eşleşme olmaz. Beşinci satır ise kılavuzun önerdiği bir alternatiftir: köşeli parantez içinde nokta özel anlamını kaybeder, yani [.] tırnak ve ters eğik çizgi gerektirmeden gerçek noktaya uyar.
Tüm eşleşmeleri bulmak
=~ metindeki ilk eşleşmeyi bulur. Bir metindeki tüm eşleşmeleri işlemek için yaygın bir yol, her başarılı eşleşmeden sonra metnin işlenmiş kısmını kesip döngüye devam etmektir. Aşağıda ${metin#*"${BASH_REMATCH[0]}"} ifadesi, metnin başından ilk eşleşmenin sonuna kadar olan kısmı siliyor; eşleşme parçası tırnak içinde olduğu için içindeki karakterler glob olarak yorumlanmıyor.
metin="a=1, b=22, c=333"
desen='([a-z])=([0-9]+)'
while [[ $metin =~ $desen ]]; do
echo "${BASH_REMATCH[1]} değeri ${BASH_REMATCH[2]}"
metin=${metin#*"${BASH_REMATCH[0]}"}
done
# çıktı:
# a değeri 1
# b değeri 22
# c değeri 333Bu ifadenin ayrıntıları, yani # ile en kısa öneki silme ve tırnaklı desen kullanımı string işlemleri yazısında anlatılıyor. Desen boş metne de uyabiliyorsa (örneğin [0-9]*) döngü her turda boş bir eşleşme bulup ilerlemeyebilir; böyle desenlerde en az bir karakter isteyen + niceleyicisini kullanın.
Birden fazla koşulu birleştirmek
[[ ]] içindeki ifadeler &&, || ve ! ile birleştirilebilir ve kılavuza göre && ile || sonucu belirlemeye yetecek kadar ifadeyi değerlendirdikten sonra durur. Bu, birden fazla =~ sınamasını tek koşulda toplamanızı sağlar. Ancak BASH_REMATCH açısından dikkat edilecek bir nokta vardır: dizi, en son çalışan =~ sınamasının sonucunu tutar. Aşağıdaki örnekte ikinci sınama ! ile tersine çevrilmiş bir eşleşmedir; prod değeri desene uymadığı için koşul doğru çıkıyor, ama dizi bu başarısız eşleşmeden sonra boşalmış oluyor ve ilk sınamanın yakaladığı numara kayboluyor.
ad="sunucu01"
ortam="prod"
if [[ $ad =~ ^sunucu([0-9]+)$ && ! $ortam =~ ^(test|dev)$ ]]; then
echo "üretim sunucusu"
echo "BASH_REMATCH öğe sayısı: ${#BASH_REMATCH[@]}"
fi
# çıktı:
# üretim sunucusu
# BASH_REMATCH öğe sayısı: 0Yakaladığınız gruplara ihtiyacınız varsa sınamaları ayırın: önce grupları yakalayan eşleşmeyi yapın ve değerleri kopyalayın, diğer koşulları ondan sonra kontrol edin.
Büyük/küçük harf duyarsız eşleşme
Kılavuza göre nocasematch kabuk seçeneği açıkken =~ eşleşmesi alfabetik karakterlerin büyük veya küçük olmasına bakmadan yapılır. Seçenek aynı zamanda == ve case desenlerini de etkilediği için işiniz bitince kapatmanız iyi olur. shopt seçenekleri shopt yazısında toplu olarak anlatılıyor.
shopt -s nocasematch
[[ "HATA: disk" =~ ^hata ]] && echo "büyük/küçük harf yok sayıldı"
shopt -u nocasematch
[[ "HATA: disk" =~ ^hata ]] || echo "artık ayırt ediliyor"
# çıktı:
# büyük/küçük harf yok sayıldı
# artık ayırt ediliyorTürkçe harflerde dikkatli olun: büyük/küçük harf eşlemesi yerel ayara (locale) bağlıdır ve örneklerimizi çalıştırdığımız C.UTF-8 Türkçe bir yerel ayar değildir. İ ile i ve I ile ı arasındaki Türkçeye özgü eşlemenin beklediğiniz gibi olacağını varsaymayın; kendi sisteminizde deneyin.
Desteklenmeyen sözdizimi ve yanlış komut
ERE'de rakamlar için \d kısaltması tanımlı değildir. Aşağıda \d ile yazılan desen bizim sistemimizde eşleşmedi; [0-9] ve [[:digit:]] ise eşleşti. Taşınabilir olmak için her zaman köşeli parantezli karakter sınıflarını kullanın.
[[ "123" =~ ^\d+$ ]]; echo "\\d ile: $?"
[[ "123" =~ ^[0-9]+$ ]]; echo "[0-9] ile: $?"
[[ "123" =~ ^[[:digit:]]+$ ]]; echo "[[:digit:]] ile: $?"
# çıktı:
# \d ile: 1
# [0-9] ile: 0
# [[:digit:]] ile: 0=~ tek köşeli parantezli test / [ komutunda yoktur. Yanlışlıkla [ ... =~ ... ] yazarsanız [ komutu =~ işlecini tanımaz ve "binary operator expected" hatasıyla 2 döndürür. İki yapı arasındaki diğer farklar test ve [[ ]] farkı yazısında anlatılıyor.
[ "abc" =~ b ]
echo "durum: $?"
# çıktı:
# ornek.sh: line 1: [: =~: binary operator expected
# durum: 2Glob mu, regex mi?

[[ ]] içinde iki tür desen eşleştirme vardır. == ve != sağ taraftaki ifadeyi bir glob deseni olarak, sanki extglob seçeneği açıkmış gibi yorumlar ve metnin tamamına uymasını ister. =~ ise düzenli ifade kullanır ve metnin bir kısmına uyması yeterlidir. Dosya uzantısı kontrolü gibi basit işler için glob daha okunaklıdır; yapılandırılmış metinden parça çıkarmanız gerektiğinde =~ ve BASH_REMATCH öne çıkar. Glob tarafı için joker karakterler ve extglob yazılarına bakabilirsiniz.
dosya="yedek.tar.gz"
[[ $dosya == *.gz ]] && echo "glob: .gz ile bitiyor"
[[ $dosya =~ \.gz$ ]] && echo "regex: .gz ile bitiyor"
[[ $dosya =~ ^yedek ]] && echo "regex: yedek ile başlıyor"
# çıktı:
# glob: .gz ile bitiyor
# regex: .gz ile bitiyor
# regex: yedek ile başlıyor| Özellik | == (glob) | =~ (regex) |
|---|---|---|
| Desen dili | kabuk deseni, extglob dahil | POSIX ERE |
| Eşleşme kapsamı | metnin tamamı | metnin herhangi bir parçası |
| Parça yakalama | yok | BASH_REMATCH |
| Tırnaklanan kısım | düz metin | düz metin |
| Hatalı desen | ayrı bir kod yok (0 veya 1) | durum 2 |
Kısa uygulama: günlük satırını ayrıştırmak
Bir günlük dosyasındaki satırların tarih, saat, seviye ve mesaj alanlarına ayrılması gerektiğini düşünün. Deseni bir değişkende tanımlayıp her alanı bir grupla yakalamak, alan sırasını açıkça belgelemiş olur. Aşağıda seviye grubu yalnızca üç değeri kabul ediyor; bu sayede biçime uymayan satırlar otomatik olarak ayıklanıyor. Son grup (.*) satırın geri kalanını, içindeki iki noktalar ve eğik çizgilerle birlikte alıyor.
satir="2026-11-07 12:30:01 ERROR disk dolu: /var"
desen='^([0-9-]+) ([0-9:]+) (INFO|WARN|ERROR) (.*)$'
if [[ $satir =~ $desen ]]; then
tarih=${BASH_REMATCH[1]}
seviye=${BASH_REMATCH[3]}
mesaj=${BASH_REMATCH[4]}
echo "[$seviye] $tarih: $mesaj"
fi
# çıktı: [ERROR] 2026-11-07: disk dolu: /varBu örneği bir while read -r satir döngüsüyle bir dosyanın tamamına uygulayabilirsiniz. Kabuk içi eşleşme, betiğin akışına karar vermek ve birkaç alanı değişkenlere almak için biçilmiş kaftandır; bir dosyanın tamamını süzmek gibi toplu işleri ise bu amaçla yazılmış grep veya awk gibi araçlara bırakmak da bir seçenektir. Günlük dosyalarının nerede tutulduğu ve nasıl okunduğu için Linux loglarını okuma yazısına bakabilirsiniz.
Sık Sorulan Sorular
Neden desenim tırnak içindeyken çalışmıyor?
Çünkü tırnaklanan kısım düz metin olarak aranır. [[ $x =~ "^[0-9]+$" ]] satırı, metnin içinde tam olarak ^[0-9]+$ karakterlerinin geçip geçmediğine bakar. Deseni tırnaksız yazın ya da bir değişkene atayıp tırnaksız kullanın.
BASH_REMATCH'i local yapabilir miyim?
Kılavuz bunun beklenmedik sonuçlara yol açacağını söylüyor, çünkü Bash diziyi her zaman genel kapsamda ayarlar. Fonksiyonda değerlere ihtiyacınız varsa eşleşmeden hemen sonra kendi local değişkenlerinize kopyalayın.
Türkçe karakterler [[:alpha:]] ile eşleşir mi?
Karakter sınıflarının hangi harfleri kapsadığı yerel ayara bağlıdır. Sonuç, betiğin çalıştığı sistemin yerel ayarına göre değişebilir; önemliyse hedef sistemde deneyin.
=~ ile eşleşmeyi tersine çevirebilir miyim?
Evet, [[ ! $metin =~ $desen ]] biçiminde ! kullanılabilir. Kılavuz [[ ]] içindeki ifadelerin !, && ve || ile birleştirilebileceğini belirtir.
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 · test ve [[ ]] farkı · extglob desenleri · String işlemleri · if, elif, else · Regex başlangıç rehberi