Go'da düzenli ifadeler standart kütüphanedeki regexp paketiyle kullanılır: ifadeyi bir kez regexp.MustCompile veya regexp.Compile ile derlersiniz, dönen *regexp.Regexp değeriyle MatchString, FindString, FindAllString, FindStringSubmatch ve ReplaceAllString gibi metotları çağırırsınız. Sözdizimi Perl ve Python'a benzer ama RE2 sözdizimidir: geri başvuru (\1) ve ileri/geri bakış ((?=...)) desteklenmez. Karşılığında paket, eşleştirmenin girdi uzunluğuyla doğrusal sürede çalışacağını garanti eder.

Bu yazıda Go'ya özgü olanı anlatıyoruz: metot adlarının sistematiği, alt eşleşmeler ve adlı gruplar, değiştirme şablonlarındaki $1 ve ${ad} kuralı, Unicode ve Türkçe karakterlerle davranış, desteklenmeyen sözdiziminin verdiği hatalar ve performans için ifadeleri bir kez derleme alışkanlığı. Örnekler Go 1.27.3 ile çalıştırıldı. Düzenli ifadelerin genel mantığını ve temel sözdizimini yeni öğreniyorsanız önce düzenli ifadeler başlangıç rehberine, dizinin tamamı için Go öğrenme rehberine bakabilirsiniz.

RE2 sözdizimi ve doğrusal zaman garantisi

Paket belgesi, kabul edilen sözdiziminin RE2 sözdizimiyle aynı olduğunu (tek istisna \C) ve uygulamanın girdi boyutuna göre doğrusal sürede çalışmasının garanti edildiğini belirtir; belge bu özelliğin çoğu açık kaynak düzenli ifade uygulamasında garanti edilmediğini de ekler. Bu, dışarıdan gelen metinlere düzenli ifade uyguladığınızda kötü yazılmış bir ifadenin geri izleme (backtracking) yüzünden saniyelerce takılması gibi bir riskin olmaması demektir. Bedeli, geri izleme gerektiren özelliklerin bulunmamasıdır.

regexp/syntax belgesindeki sözdizimi listesi tek karakterler (., [xyz], \d, \p{Greek}), birleşimler (xy, x|y), tekrarlar (*, +, ?, {n,m} ve tembel biçimleri *?, +?), gruplar ((re), (?P<ad>re), (?<ad>re), (?:re)), bayraklar (i, m, s, U) ve sınırlar (^, $, \A, \z, \b) içerir. Sayaçlı tekrarlarda en küçük veya en büyük sayı 1000'i geçemez. Desteklenmeyen bir yapı yazdığınızda derleme hatası alırsınız:

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	for _, ifade := range []string{`(\w)\1`, `foo(?=bar)`, `(?<!x)y`, `[a-`, `a{1001}`} {
		_, err := regexp.Compile(ifade)
		fmt.Println(err)
	}

	re := regexp.MustCompile(`a(|b)`)
	fmt.Println(re.FindString("ab"))
	posix := regexp.MustCompilePOSIX(`a(|b)`)
	fmt.Println(posix.FindString("ab"))

	ok, err := regexp.MatchString(`^\d{5}$`, "34710")
	fmt.Println(ok, err)
}

// Çıktı:
// error parsing regexp: invalid escape sequence: `\1`
// error parsing regexp: invalid or unsupported Perl syntax: `(?=`
// error parsing regexp: invalid named capture: `(?<!x)y`
// error parsing regexp: missing closing ]: `[a-`
// error parsing regexp: invalid repeat count: `{1001}`
// a
// ab
// true <nil>

Geri bakış ifadesi (?<!x) ilginç bir hata verir: RE2 sözdiziminde (?<ad>...) adlı grup anlamına geldiği için ayrıştırıcı bunu geçersiz bir grup adı olarak yorumlar. Son iki eşleşme, eşleştirme anlamındaki farkı gösterir. Compile ile derlenen ifadeler Perl ve Python gibi "en soldaki ilk" (leftmost-first) kuralını izler: alternatiflerden ilk yazılanı tercih eder, bu yüzden a(|b) "ab" içinde yalnızca "a" bulur. CompilePOSIX ifadeyi POSIX ERE sözdizimiyle sınırlar ve "en soldaki en uzun" (leftmost-longest) eşleşmeyi seçer. Aynı davranışı derlenmiş bir ifadede Longest() metoduyla da açabilirsiniz; belge bu metodun ifadeyi değiştirdiğini ve başka metotlarla eşzamanlı çağrılamayacağını belirtir.

Metot adlarının sistematiği

Metot adlarının sistematiği: Find, FindAll, String, Submatch

Regexp türünde eşleşme bulan çok sayıda metot vardır ama adları tek bir kalıpla üretilmiştir. Belge kalıbı yine bir düzenli ifadeyle verir: (Find|FindAll)(String)?(Submatch)?(Index)?. Adın her parçası bir anlam taşır; bu tabloyu bildiğinizde hangi metodu arayacağınızı tahmin edebilirsiniz:

Ad parçasıAnlamı
Findyalnızca ilk eşleşme
FindAllörtüşmeyen tüm eşleşmeler; n argümanı en fazla kaç tane (negatifse hepsi)
Stringargüman ve sonuç string; yoksa []byte
Submatchsonuç, parantezli grupların eşleşmelerini de içeren bir dilim; 0. öğe eşleşmenin tamamı
Indexmetin yerine bayt konum çiftleri döner
Match, MatchStringyalnızca eşleşme olup olmadığını bool olarak verir

Önemli bir kural: String sürümlerinde boş sonuç ya eşleşme olmadığını ya da boş bir eşleşmeyi ifade eder. Bu ikisini ayırmanız gerekiyorsa Index sürümünü veya Submatch sürümünün nil dönüp dönmediğini kontrol edin. Aşağıdaki örnek bir metinden Türk plaka numaralarını bulup parçalarına ayırır:

go
package main

import (
	"fmt"
	"regexp"
)

var plaka = regexp.MustCompile(`\b(\d{2}) ?([A-Z]{1,3}) ?(\d{2,4})\b`)

func main() {
	metin := "Park eden araçlar: 34 ABC 123, 06AB1234 ve 35 K 99."

	fmt.Println(plaka.MatchString(metin))
	fmt.Println(plaka.FindString(metin))
	fmt.Println(plaka.FindStringIndex(metin))
	fmt.Printf("%q\n", plaka.FindAllString(metin, -1))
	fmt.Printf("%q\n", plaka.FindAllString(metin, 2))
	fmt.Printf("%q\n", plaka.FindStringSubmatch(metin))

	for _, m := range plaka.FindAllStringSubmatch(metin, -1) {
		fmt.Println("il:", m[1], "harf:", m[2], "sayı:", m[3])
	}
	fmt.Printf("%q\n", plaka.FindString("plaka yok"))
	fmt.Println(plaka.FindStringSubmatch("plaka yok") == nil)
}

// Çıktı:
// true
// 34 ABC 123
// [20 30]
// ["34 ABC 123" "06AB1234" "35 K 99"]
// ["34 ABC 123" "06AB1234"]
// ["34 ABC 123" "34" "ABC" "123"]
// il: 34 harf: ABC sayı: 123
// il: 06 harf: AB sayı: 1234
// il: 35 harf: K sayı: 99
// ""
// true

FindStringIndex sonucu [20 30] bir bayt aralığıdır: metin[20:30] eşleşen kısmı verir. Metindeki "ç" iki bayt tuttuğu için bu konum karakter sırasından farklıdır; Go'da string'lerin bayt dizisi olduğu string, rune ve byte yazısında ayrıntılı anlatılıyor. İfade paket düzeyinde bir değişkende MustCompile ile derlendi. MustCompile, ifade hatalıysa panik üretir; belgeye göre amacı derlenmiş ifadeleri tutan global değişkenlerin güvenle başlatılmasıdır. İfade sabit olduğu için hata programın ilk çalıştırılışında hemen ortaya çıkar. Kullanıcıdan gelen bir ifadeyi derlerken ise hatayı döndüren Compile kullanın.

Adlı gruplar ve SubexpIndex

Adlı gruplar ve SubexpIndex

Grupları numarayla izlemek, ifade değiştikçe hataya açıktır. (?P<ad>...) veya kısa biçimi (?<ad>...) ile gruplara ad verebilirsiniz. SubexpNames grup adlarını, NumSubexp grup sayısını, SubexpIndex(ad) ise adı verilen grubun indeksini döndürür; ad yoksa sonuç -1 olur. Dilimin 0. öğesi her zaman eşleşmenin tamamıdır ve adı boş string'tir:

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	re := regexp.MustCompile(`(?P<yil>\d{4})-(?P<ay>\d{2})-(?P<gun>\d{2})`)
	m := re.FindStringSubmatch("Son kontrol 2026-11-07 tarihinde yapıldı.")

	fmt.Println(re.NumSubexp(), re.SubexpNames())
	fmt.Println(m[re.SubexpIndex("gun")], m[re.SubexpIndex("ay")], m[re.SubexpIndex("yil")])

	alanlar := map[string]string{}
	for i, ad := range re.SubexpNames() {
		if i > 0 && ad != "" {
			alanlar[ad] = m[i]
		}
	}
	fmt.Println(alanlar)

	fmt.Println(re.ReplaceAllString("2026-11-07", "$gun.$ay.$yil"))
	fmt.Println(re.SubexpIndex("saat"))
}

// Çıktı:
// 3 [ yil ay gun]
// 07 11 2026
// map[ay:11 gun:07 yil:2026]
// 07.11.2026
// -1

Tarih ayrıştırmak için düzenli ifade kullanmak yalnızca biçimi doğrular; 2026-13-45 gibi geçersiz bir tarih de eşleşir. Gerçek tarih kontrolü için eşleşen parçayı time.Parse ile ayrıştırın. Sayısal grupları sayıya çevirmek için strconv.Atoi kullanılır.

Değiştirme: ReplaceAllString ve şablon kuralları

ReplaceAllString(kaynak, şablon) tüm eşleşmeleri şablonla değiştirir ve şablondaki $ işaretlerini Expand metodundaki gibi yorumlar. $1 numaralı gruba, $ad adlı gruba başvurur; ${1} ve ${ad} biçimleri adın nerede bittiğini açıkça belirtir. Belgedeki kurala göre $ad biçiminde ad mümkün olduğunca uzun alınır: $1x, ${1}x değil ${1x} demektir. Olmayan bir grup boş string ile değiştirilir ve hata verilmez. Düz bir dolar işareti için $$ yazılır. Şablonu hiç yorumlamak istemiyorsanız ReplaceAllLiteralString, her eşleşme için kod çalıştırmak istiyorsanız ReplaceAllStringFunc kullanın:

go
package main

import (
	"fmt"
	"regexp"
	"strings"
)

func main() {
	re := regexp.MustCompile(`(\w+)@(\w+)\.com`)
	s := "ali@ornek.com, veli@test.com"

	fmt.Println(re.ReplaceAllString(s, "$1 [at] $2"))
	fmt.Printf("%q\n", re.ReplaceAllString(s, "$1_x"))
	fmt.Println(re.ReplaceAllString(s, "${1}_x"))
	fmt.Println(re.ReplaceAllLiteralString(s, "$1"))
	fmt.Println(re.ReplaceAllString(s, "$$1"))

	fmt.Println(re.ReplaceAllStringFunc(s, strings.ToUpper))

	bosluk := regexp.MustCompile(`\s+`)
	fmt.Printf("%q\n", bosluk.ReplaceAllString("  çok   fazla\t boşluk  ", " "))
	fmt.Printf("%q\n", bosluk.Split("a  b\tc\n d", -1))
	fmt.Printf("%q\n", regexp.MustCompile(`[,;]\s*`).Split("elma, armut;ayva", 2))

	fiyat := "1.250,00 TL (indirim: %10)"
	q := regexp.QuoteMeta(fiyat)
	fmt.Println(q)
	fmt.Println(regexp.MustCompile(q).MatchString("Toplam: " + fiyat))
}

// Çıktı:
// ali [at] ornek, veli [at] test
// ", "
// ali_x, veli_x
// $1, $1
// $1, $1
// ALI@ORNEK.COM, VELI@TEST.COM
// " çok fazla boşluk "
// ["a" "b" "c" "d"]
// ["elma" "armut;ayva"]
// 1\.250,00 TL \(indirim: %10\)
// true

İkinci satır bu yazının en önemli uyarısıdır: "$1_x" şablonunda alt çizgi de ad karakteri sayıldığı için Go 1_x adlı bir grup arar, bulamaz ve boş string koyar; sonuç yalnızca ayırıcı virgül ve boşluktan ibaret kalır (bunu görünür kılmak için o satırda %q kullandık). Grubun ardından harf, rakam veya alt çizgi gelecekse her zaman ${1} yazın. Split metodu ifadeyi ayırıcı olarak kullanır; n argümanı en fazla kaç parça döneceğini sınırlar ve son parça kalan metnin tamamını içerir. QuoteMeta ise bir metindeki bütün meta karakterleri kaçışlar; kullanıcının yazdığı bir aramayı ifadeye düz metin olarak gömmek için kullanılır.

Unicode, Türkçe karakterler ve bayraklar

Paket belgesine göre tüm karakterler UTF-8 kod noktaları olarak işlenir; . bir baytı değil bir rune'u eşler. Ancak Perl karakter sınıfları yalnızca ASCII kapsar: \w tam olarak [0-9A-Za-z_], \d [0-9], \s [\t\n\f\r ] demektir. \b sözcük sınırı da ASCII sözcük karakterlerine göre çalışır. Türkçe metinde harfleri yakalamak için Unicode sınıflarını kullanın: \p{L} tüm harfleri, \p{Lu} büyük harfleri, \p{N} sayıları eşler. Go 1.25 sürüm notları bu sınıflarda \p{Letter} gibi uzun kategori adlarının ve Any, ASCII gibi yeni adların kabul edildiğini, adların artık büyük-küçük harf, boşluk, alt çizgi ve tire gözetmeden arandığını belirtir.

go
package main

import (
	"fmt"
	"regexp"
)

func main() {
	s := "Çağrı ışık söğüt"

	fmt.Printf("%q\n", regexp.MustCompile(`\w+`).FindAllString(s, -1))
	fmt.Printf("%q\n", regexp.MustCompile(`\p{L}+`).FindAllString(s, -1))
	fmt.Printf("%q\n", regexp.MustCompile(`[\p{L}\d_]+`).FindAllString("ürün_42 fiyatı", -1))

	fmt.Println(regexp.MustCompile(`(?i)istanbul`).MatchString("ISTANBUL"))
	fmt.Println(regexp.MustCompile(`(?i)istanbul`).MatchString("İSTANBUL"))
	fmt.Println(regexp.MustCompile(`(?i)şeker`).MatchString("ŞEKER"))

	satirlar := "ad: Ece\nyas: 31\nsehir: Bursa"
	fmt.Printf("%q\n", regexp.MustCompile(`^\w+`).FindAllString(satirlar, -1))
	fmt.Printf("%q\n", regexp.MustCompile(`(?m)^\w+`).FindAllString(satirlar, -1))

	fmt.Println(regexp.MustCompile(`<.+>`).FindString("<b>kalın</b>"))
	fmt.Println(regexp.MustCompile(`<.+?>`).FindString("<b>kalın</b>"))

	fmt.Println(regexp.MustCompile(`\d+`).FindStringIndex("ğ12"))
}

// Çıktı:
// ["a" "r" "k" "s" "t"]
// ["Çağrı" "ışık" "söğüt"]
// ["ürün_42" "fiyatı"]
// true
// false
// true
// ["ad"]
// ["ad" "yas" "sehir"]
// <b>kalın</b>
// <b>
// [2 4]

İlk satır \w ile Türkçe metnin nasıl parçalandığını gösteriyor: yalnızca ASCII harfler kaldı. (?i) bayrağı büyük-küçük harf duyarsız eşleştirme yapar; "ş" ile "Ş" eşleşti, ama "i" ile "İ" eşleşmedi. Büyük-küçük harf eşlemesi dile göre değişmeyen Unicode kurallarıyla yapıldığı ve bu kurallarda "i" harfinin büyüğü noktasız "I" olduğu için Türkçedeki i/İ ve ı/I ikilileri (?i) ile yakalanmaz. Türkçe metinlerde duyarsız arama gerekiyorsa ifadeye [iİ] gibi açık sınıflar yazın ya da karşılaştırmadan önce metni Türkçe kurallarla küçük harfe çevirin; bunun için strings.ToLowerSpecial(unicode.TurkishCase, s) kullanımı string yazısında anlatılıyor.

^ ve $ varsayılan olarak metnin başını ve sonunu eşler; satır başlarını eşlemek için (?m) bayrağı gerekir. s bayrağı . karakterinin satır sonunu da eşlemesini sağlar. Tekrarlar varsayılan olarak açgözlüdür: <.+> mümkün olan en uzun parçayı alır, <.+?> ise en kısasını. Yine de HTML ayrıştırmak için düzenli ifade yerine bir ayrıştırıcı kullanmak daha güvenlidir; düzenli ifade iç içe yapıları doğru izleyemez.

Performans ve eşzamanlı kullanım

Aynı ifadeyi döngü veya HTTP işleyicisi içinde her seferinde yeniden derlemek gereksiz iş yapmaktır; ifadeyi paket düzeyinde bir kez derleyin; paket belgesindeki ilk örnek de ifadeyi "genellikle başlatma sırasında bir kez" derlemeyi önerir. Belgeye göre bir *regexp.Regexp değeri, Longest gibi yapılandırma metotları dışında, birden fazla goroutine tarafından aynı anda güvenle kullanılabilir; yani global bir ifadeyi tüm istek işleyicileri paylaşabilir. Paket düzeyindeki regexp.MatchString(ifade, metin) fonksiyonu ifadeyi string olarak aldığı için derlenmiş bir değer saklamaz; belge de daha karmaşık sorgular için Compile ve Regexp türünün kullanılmasını söyler. Tek seferlik kontroller için uygundur. Basit alt metin aramaları için düzenli ifadeye hiç gerek yoktur: strings.Contains, strings.HasPrefix veya strings.Fields niyeti daha açık anlatır.

Sık yapılan hatalar

Sık yapılan hatalar: İfadeyi döngü içinde regexp.MustCompile ile tekrar tekrar derlemek. Bir kez..., Kullanıcıdan gelen ifadeyi MustCompile ile derlemek. Hatalı ifade programı...
  • İfadeyi döngü içinde regexp.MustCompile ile tekrar tekrar derlemek. Bir kez paket düzeyinde derleyin.
  • Kullanıcıdan gelen ifadeyi MustCompile ile derlemek. Hatalı ifade programı panikle durdurur; Compile kullanıp hatayı işleyin.
  • Türkçe metinde \w veya \b kullanmak. İkisi de yalnızca ASCII sözcük karakterlerini tanır; \p{L} kullanın.
  • Değiştirme şablonunda $1_ veya $1abc yazmak. Ad olabildiğince uzun okunur; ${1} kullanın.
  • Geri başvuru veya ileri bakış gerektiren bir ifadeyi başka bir dilden taşımak. RE2 bunları desteklemez; mantığı ikiye bölüp Go koduyla tamamlayın.
  • FindString sonucunun boş olmasını "eşleşme yok" diye yorumlamak. Boş bir eşleşme de boş string döndürür; ayırmak için FindStringIndex sonucunun nil olup olmadığına bakın.
  • Bayt konumlarını karakter sırası sanmak. Index sonuçları bayt cinsindendir.

Sık Sorulan Sorular

Go regexp neden lookahead ve geri başvuruyu desteklemiyor?

Paket RE2 sözdizimini uygular ve girdi uzunluğuyla doğrusal sürede çalışmayı garanti eder. Geri başvuru ve bakış ifadeleri bu garantiyle bağdaşmayan geri izleme gerektirir. Bu özellikleri yazmaya çalışırsanız invalid escape sequence veya invalid or unsupported Perl syntax hatası alırsınız.

Compile ile MustCompile arasındaki fark nedir?

Compile derlenmiş ifadeyle birlikte bir error döndürür. MustCompile hata durumunda panik üretir ve yalnızca ifadeyi döndürür; sabit ifadeleri tutan global değişkenler için tasarlanmıştır.

Büyük-küçük harf duyarsız arama nasıl yapılır?

İfadenin başına (?i) ekleyin: (?i)merhaba. Türkçe i/İ ve ı/I ikilileri bu bayrakla eşleşmez; bu harfler için açık karakter sınıfları yazın.

Tüm eşleşmeleri ve gruplarını nasıl alırım?

FindAllStringSubmatch(metin, -1) her eşleşme için bir dilim döndürür; her dilimin 0. öğesi eşleşmenin tamamı, sonraki öğeler gruplardır. İkinci argüman negatifse tüm eşleşmeler, sıfır veya pozitifse en fazla o kadar eşleşme döner.

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: Go öğrenme rehberi · Düzenli ifadeler başlangıç rehberi · string, rune ve byte · strconv ile sayı dönüşümü · time paketi · Go test yazma