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

=~ 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.

bash
desen='a(b'
[[ abc =~ $desen ]]
echo "durum: $?"
# çıktı: durum: 2

Kapanmamış 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.

bash
[[ "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.

bash
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: reddedildi

BASH_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.

bash
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: 07

Kı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:

bash
[[ "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 öğe

Bu 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.

bash
[[ "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): 0

Birinci 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.

bash
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: 1

Kı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

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.

bash
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=7

Ters 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:

bash
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.

bash
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 333

Bu 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.

bash
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ı: 0

Yakaladığı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.

bash
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 ediliyor

Tü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.

bash
[[ "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.

bash
[ "abc" =~ b ]
echo "durum: $?"
# çıktı:
#   ornek.sh: line 1: [: =~: binary operator expected
#   durum: 2

Glob mu, regex mi?

Glob mu, regex mi? Desen dili, Eşleşme kapsamı, Parça yakalama, Tırnaklanan kısım

[[ ]] 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.

bash
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 dilikabuk deseni, extglob dahilPOSIX ERE
Eşleşme kapsamımetnin tamamımetnin herhangi bir parçası
Parça yakalamayokBASH_REMATCH
Tırnaklanan kısımdüz metindüz metin
Hatalı desenayrı 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.

bash
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: /var

Bu ö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