Pratikte Regex: Çapalar, Niceleyiciler ve Yakalama Grupları

2026-04-13 tarihinde yayınlandı 8 dk okuma

Özet (TL;DR)

2 Temmuz 2019’da, Cloudflare’in WAF’ının içindeki tek bir regex — .*(?:.*=.*) biçiminde bir paten deseni — küresel trafiği 27 dakika boyunca neredeyse %50 düşürdü. PCRE’nin geri izlemesi bir CPU çekirdeğini %100’e sabitledi ve her istek onun arkasında takıldı. Regex’in öğrettiği en dürüst ders şudur: kendi maliyetini gizler. Çoğu gün küçük, hızlı ve baştan çıkarıcıdır; kötü bir girdi bir sistemi durdurabilir. Bu yüzden bu yazı tonu hafif şüpheci. Günlük regex işinin çoğu bir avuç parçaya dayanır: bir deseni başa, sona veya bir sözcük sınırına bağlayan çapalar; “bu karakterlerden herhangi biri” tanımlayan karakter sınıfları; “kaç tane” diyen niceleyiciler ve yakalamanıza, başvurmanıza veya seçenek sunmanıza olanak veren gruplar. Bunları doğru anlarsanız tipik sorunlar — kaba bir e-posta doğrulamak, bir günlük satırından alanları seçmek, bir telefon numarasını normalize etmek — kısa ve okunur hale gelir. Yanlış anlarsanız, Cloudflare olayındaki gibi çok fazla, çok az eşleşen veya motoru durduran desenlerle sonuçlanırsınız. HTML, JSON veya XML’i regex ile ayrıştırmayın — düzenli diller dengeli iç içe geçmeyi tanımlayamaz. Motorlar da farklıdır: ECMAScript regex, PCRE (Perl/PHP), Python’un re’si, Oniguruma (Ruby ve Rust’ın onig crate’i) ve Go’nun RE2’si ince farklılıklara sahiptir. Cloudflare’in olayı yalnızca motor PCRE olduğu için yaşandı; RE2 altında doğrusal zamanda tamamlanırdı.

Arka plan ve kavramlar

Düzenli ifade, dizeleri eşlemek için kompakt bir dilbilgisidir. En sık başvurulan parçaları açıkça adlandırmaya değer.

Çapalar karakter tüketmezler — bir konumu öne sürerler. ^ girdinin başıdır (çok satır modunda bir satırın başı), $ sondur ve \b bir sözcük sınırıdır (\w ile \w olmayan arasındaki geçiş). Çapasız, abc daha uzun bir dizenin herhangi bir yerinde eşleşir. Onlarla, ^abc$ yalnızca tam dizenin abc olduğunu eşler ve \babc\b tek başına bir sözcük olarak abc’yi eşler.

Karakter sınıfları bir kümeden tek bir karakteri tanımlar. [abc]; a, b veya c’dir; [a-z] herhangi bir küçük harftir; [^0-9] “bir rakam dışında her şey”dir. Kısa yol sınıfları yaygın durumları kapsar: \d (rakamlar), \w (sözcük karakterleri — harfler, rakamlar, alt çizgi), \s (boşluk). Olumsuzlamalar \D, \W, \S tersleridir. . varsayılan olarak satır sonu dışında herhangi bir karakterle eşleşir; s (dotall) bayrağı bunu değiştirir.

Niceleyiciler önceki belirtece eklenir ve kaç kez tekrarlanabileceğini söyler. * sıfır veya daha fazla, + bir veya daha fazla, ? sıfır veya bir ve {n,m} “n ile m arasında”dır (her iki sınır da atlanabilir). Varsayılan olarak niceleyiciler açgözlüdür: mümkün olduğunca çok eşleşir ve yalnızca desenin geri kalanı başarısız olursa geri verir. Tembel biçim için ? ekleyin — *?, +?, ?? — ki mümkün olduğunca az eşleşir ve yalnızca zorlandığında genişler. Bazı motorlar, açgözlü eşleşen ama başarısızlıkta geri vermeyi reddeden sahiplenici niceleyiciler (*+, ++) ekler; bu, patolojik girdi üzerinde felaket geri izlemeyi önleyebilir — Cloudflare olayında eksik olan araç tam da buydu.

Gruplar alt desenleri sarar. (pattern), yakalayan bir gruptur — açılış parantezi sırasında 1’den numaralandırılır — eşleşmesi desen içinde \1 ile veya ana bilgisayar dilinde indeksli erişimcilerle başvurulabilir. (?<ad>pattern), adlandırılmış bir yakalama ekler. (?:pattern), yakalamayan bir gruptur ve yalnızca seçim ((?:cat|dog)) veya eşleşmeyi kaydetmeden niceleyici gruplaması için kullanılır. Bir grup içindeki | seçenektir: alternatiflerden birini eşleştirin.

Son olarak, bayraklar motor davranışını değiştirir: i büyük/küçük harf duyarsız, m çok satırlı (^ ve $ satır sınırlarını eşler), s dotall, u Unicode. JavaScript’te u bayrağı ayrıca “herhangi bir harf” için \p{L} gibi Unicode özellik kaçışlarını etkinleştirir.

Karşılaştırma ve veriler

NiceleyiciAçgözlü (varsayılan)Tembel (? soneki)Sahiplenici (desteklendiği yerde)
* / *? / *+Mümkün olduğunca çok eşle, başarısızlıkta geri izleMümkün olduğunca az eşle, başarısızlıkta genişleMümkün olduğunca çok eşle, geri izleme yok
+ / +? / ++Bir veya daha fazla, açgözlüBir veya daha fazla, tembelBir veya daha fazla, geri izleme yok
? / ?? / ?+Sıfır veya bir, biri tercih etSıfır veya bir, sıfırı tercih etSıfır veya bir, geri izleme yok
{n,m} / {n,m}? / {n,m}+Aralık, açgözlüAralık, tembelAralık, geri izleme yok

Açgözlü, varsayılan olarak yeterince sıklıkla doğru seçim olduğu için varsayılandır. Tembellik, “desenin geri kalanı”nın kendisi izin verici olduğunda önemlidir — örneğin, birden çok etiketi yutabilecek açgözlü .* yerine <b>(.*?)</b> ile bir HTML parçasından <b>...</b>’yi çekmek (ve yine de gerçek HTML’i regex ile ayrıştırmayın). Sahiplenici niceleyiciler ve atomik gruplar (?>...), bir desen aksi halde yakın eşleşmelerde üstel olarak çok geri izleme yolunu yeniden keşfedecekse yardımcı olur. PCRE, Java ve Oniguruma bunları destekler; ECMAScript ve Python re tarihsel olarak desteklemedi, ancak Python 3.11 re modülüne atomik gruplar ekledi.

Gerçek senaryolar

Senaryo 1 — Pragmatik bir e-posta eşleşmesi. RFC 5322’den tam e-posta dilbilgisi; yorumları, tırnaklı yerel kısımları ve iç içe IP hazır bilgileri kabul eder; hepsini kapsayan bir regex kötü ünlü biçimde canavarımsıdır (en ünlü deneme 6.425 karakter uzunluğundadır) ve hâlâ gerçek bir ayrıştırıcı değildir. Üretimde gerçekten gönderdiğim desen neredeyse her zaman ^[^\s@]+@[^\s@]+\.[^\s@]+$’tir — “boş değil, boşluksuz, serseri @ yok ve alan adında en az bir nokta” — bu, tamamen doğrulamaya kalkışmadan bariz yazım hatalarını reddeder. Bir adresi gerçekten doğrulamanın tek yolu ona bir mesaj göndermektir. Şekil için regex, varlık için e-posta kullanın.

Senaryo 2 — Uluslararası biçimlerde telefon numaraları. +82 2-1234-5678, (02) 1234-5678 ve 82-2-1234-5678’in hepsi aynı Kore Seul numarasını tanımlar. ^\+?\d{1,3}[-\s().]*\d{1,4}[-\s().]*\d{3,4}[-\s().]*\d{3,4}$ gibi bir regex ortak noktalamayı kabul eder ve ardından bir normalleştirme adımı noktalamayı kanonik bir yalnızca-rakamlar biçimine çıkarır. Ciddi herhangi bir şey için — yönlendirme, depolama, arama — kendinizinkini yuvarlamak yerine Google’ın libphonenumber’ını kullanın. Bu konuda gördüğüm en hızlı toplantı sonucu “bunu regex ile yapmıyoruz”du ve ekibe bir haftalık uç durum hatasından tasarruf ettirdi. Regex, “bu muğlak biçimde bir telefon numarasına mı benziyor” yüzeyi içindir.

Senaryo 3 — Bir günlük satırından alanları çıkarmak. 2026-04-13T02:11:05Z 192.0.2.42 "GET /search?q=foo HTTP/1.1" 200 1534 gibi bir satır tek bir desenle bölünebilir: ^(?<ts>\S+)\s+(?<ip>\S+)\s+"(?<method>\w+)\s+(?<path>\S+)\s+\S+"\s+(?<status>\d+)\s+(?<bytes>\d+)$. Adlandırılmış gruplar burada işe yarar: sonuç eşleşme nesnesi sözlük benzeridir ve her alan ada göre adreslenebilir. Günlük biçimi değiştiğinde, desen aynı zamanda ne ayrıştırdığınızın belgesidir.

Yaygın yanlış anlamalar

“Bir regex bir e-posta adresini tamamen doğrulayabilir.” Yalnızca şekli doğrulayabilir. RFC 5322, bir regex’e mantıklı biçimde kodlanamayacak kadar karmaşıktır — ve kodlasanız bile “şekil geçerli”, “posta kutusu var” anlamına gelmez. Sektör standardı desen, basit bir regex artı bir doğrulama e-postasıdır.

“Açgözlü her zaman tembelden daha yavaştır.” Doğası gereği değil. Niceleyicinin alt deseni çok kısıtlayıcı olduğunda açgözlü eşleşmeler daha hızlı olabilir; çünkü motor bir uzun ileri geçişte biter. Tembel, <b>(.*?)</b> gibi “desenin geri kalanı” eşleşmeyi çapalıyorsa kazanır. Refleksif olarak ?’ye ulaşmak yerine gerçekçi girdilerle karşılaştırın.

“Tüm regex motorları aynıdır.” Değildir. ECMAScript regex, sahiplenici niceleyicileri ve atomik grupları eksiktir (modern motorlardaki v bayrağı birkaç boşluğu kapatır ama bunları değil); Python’un re’si kendi Unicode özellik kümesine sahiptir; PCRE geri başvuruları ve özyinelemeli desenleri destekler; Oniguruma — Ruby ve Rust’ın onig crate’i tarafından kullanılan motor — yine başka bir lehçedir. Go’nun RE2’si, girdide doğrusal garanti edilen yürütme süresi karşılığında geri başvuruları ve ileri/geri bakışları düşürür — Cloudflare’in olaylarından sonra geçişi değerlendirdiği aynı motor. Bir Perl öğreticisinden kopyaladığınız desen JavaScript’te çalışmayabilir ve tersi.

“Regex HTML (veya JSON, veya XML) ayrıştırabilir.” Hayır, çünkü düzenli diller dengeli iç içe geçmeyi tanımlayamaz. Regex, yapılandırılmış metinden belirli, iyi biçimli bir alt deseni — örneğin tek bir öznitelik değerini — çıkarabilir, ancak tüm ağacı doğru biçimde ayrıştıramaz. İç içe biçimler için özel bir ayrıştırıcı kullanın (DOMParser, JSON.parse, bir XML kitaplığı, bir CSV okuyucu). Stack Overflow’un “regex vs HTML” destanı (2009 yanıtı) bir tartışma değil, uyarıcı bir hikayedir.

Kontrol listesi

  1. Girdi ne ve karşı örnekler neler? Deseni yazmadan önce her ikisini de yazın.
  2. Veri iç içe mi veya özyinelemeli mi? Öyleyse bir ayrıştırıcı kullanın. Regex yanlış araçtır.
  3. Hangi motoru hedefliyorsunuz? JavaScript, Python, Go, PCRE; ileri/geri bakış, geri başvurular, Unicode konusunda farklıdır.
  4. Eşleşmenin kendisini mi yoksa yalnızca evet/hayır cevabını mı istiyorsunuz? Yalnızca seçim veya nicelleştirme için var olan gruplar için yakalamayan (?:...) tercih edin.
  5. Desen kullanıcı tarafından sağlanıyor mu veya güvenilmeyen girdiye uygulanıyor mu? Felaket geri izlemeye karşı zaman sınırı, atomik gruplar veya RE2 gibi doğrusal zamanlı bir motorla koruyun. Aksi halde Cloudflare olursunuz.
  6. Daha sonra metin normalleştirmesi yapıyor musunuz? Her şeyi tek devasa bir desene kodlamayın; basit bir şekil kontrolünü küçük bir son işleme adımıyla eşleştirin.
  7. Regex belgelendi mi? x (genişletilmiş) modlu çok satır veya desenin üzerinde bir yorum, sonra okuyacak kişi için ucuz bir sigortadır.

İlgili araç

Patrache Studio regex test aracı, desenleri tarayıcıda örnek girdiye karşı çalıştırır ve grup yakalamalarını satır içinde gösterir; bu, sekmeler arasında gidip gelmekten daha hızlıdır. Eşleştirdiğiniz dizeler yapılandırılmışsa — JSON yükleri içeren günlük satırları, API yanıtları — regex çalışmasını JSON Biçimlendirme, Doğrulama ve Şema: Pratikte ile birleştirin; böylece çıkarılan parça ikinci bir regex yerine uygun bir ayrıştırıcıyla doğrulanır. Yaygın bir regex hedefi, bir URL’e gömülü UUID’dir; UUID v1, v4 ve v7 Karşılaştırması: Bir Veritabanı Birincil Anahtarı Seçmek aynı 36 karakterin sürüm bitlerine bağlı olarak neden farklı anlamlara gelebildiğini kapsar.

Kaynaklar