Mert Özen Her Satırda Daha İleri
Geliştirici Araçları Regular Expressions — #03

Gruplar ve Backreference: Capture, Named Group, Non-capturing, Alternation ve Replace'te $1/${name}

Mert Özen 15 Kas 2026 12 dk 4 görüntülenme
Gruplar ve Backreference: Capture, Named Group, Non-capturing, Alternation ve Replace'te $1/${name}

Capture group'lar açılış parantezinin sırasıyla numaralanır, ama .NET isimli grupları en sona koyduğu için aynı $2/$1 replacement'ı .NET'te ve JavaScript'te farklı sonuç verir. Named group yazımlarını, ES2025'in tekrar eden grup isimlerini, backreference ile tekrar eden kelimeyi, POSIX'in en uzun eşleşme kuralını ve .NET, JavaScript, sed ve VS Code'un replace syntax'ını gerçek çıktılarla karşılaştırıyor, Bookstore'un CSV'sinde yazar adlarını çeviriyoruz.

Sürüm notu: Bu yazıdaki örnekler .NET 10 (SDK 10.0.404), Node.js 26.13 (LTS), grep 3.12 (GNU), PCRE2 (Perl uyumlu regex library'si) 10.49, sed 4.10 (GNU), ripgrep 15.2.0 ve Python 3.14 ile denenmiştir. grep, sed, rg ve pcre2test komutları Linux'ta, WSL'de (Windows içindeki Linux ortamı) ya da Git Bash'te çalışır.

Önce Tahmin Et

Sitedeki serilerin ortak örnek uygulaması Bookstore'un (bir online kitapçı) raporlarında dönem 2026-11 biçiminde tutuluyor, ekranda ise 11/2026 görünmesi isteniyor. Backend'de ve Angular tarafında aynı pattern ve aynı replacement string'i kullanılıyor:

pattern:     (?<year>\d{4})-(\d{2})
replacement: $2/$1

Yıl grubuna isim verilmiş, ay grubu isimsiz. .NET'te Regex.Replace, JavaScript'te String.prototype.replace bu ikisiyle çağrılıyor. İki tarafta da 11/2026 mi çıkar?

Capture Group ve Numaralandırma Kuralı

Parantez iki iş yapar: içindeki parçayı tek bir birim hâline getirir (quantifier ya da alternation onun tamamına uygulanır) ve eşleşen metni yakalar. Yakalanan metne numarasıyla ulaşırsın. Numara, açılış parantezlerinin soldan sağa sırasına göre verilir; iç içe gruplarda da aynı kural geçerli. ((\d{4})-(\d{2})) pattern'inde dıştaki grup 1, yıl 2, ay 3 olur. Grup 0 her zaman eşleşmenin tamamıdır.

Kural basit, ama .NET'te bir istisnası var:

using System.Text.RegularExpressions;

Regex re = new(@"(?<year>\d{4})-(\d{2})");
Match m = re.Match("2026-11");

// .NET'te isimli gruplar, isimsiz grupların arkasından numaralanır
Console.WriteLine($"1={m.Groups[1].Value} 2={m.Groups[2].Value} year={m.Groups["year"].Value}");
Console.WriteLine(string.Join(",", re.GetGroupNames()));
Console.WriteLine(re.Replace("2026-11", "$2/$1"));

// aynı isim iki farklı alternatifte
Regex either = new(@"(?<year>\d{4})-\d{2}|\d{2}/(?<year>\d{4})");
Console.WriteLine($"{either.Match("2026-11").Groups["year"].Value} {either.Match("11/2026").Groups["year"].Value}");
$ dotnet run dotnet/group-numbers.cs
1=11 2=2026 year=2026
0,1,year
2026/11
2026 2026
$ node js/group-numbers.mjs
1=2026 2=11 year=2026
11/2026
2026 2026
SyntaxError: Invalid regular expression: /(?<year>\d{4})-(?<year>\d{2})/: Duplicate capture group name

Tahminin cevabı: .NET 2026/11, JavaScript 11/2026 yazdı. .NET önce isimsiz grupları 1'den başlayarak numaralıyor, isimli grupları ondan sonra. Bu yüzden $1 ay, $2 yıl oldu. JavaScript ve PCRE2 ise isimli ya da isimsiz fark etmeden açılış parantezinin sırasına bakıyor. Bir pattern'de isimli ve isimsiz grubu karıştırıyorsan numaraya güvenme; ya hepsine isim ver ya da hiçbirine.

Non-capturing Group: (?:...)

Bir parçayı yalnızca gruplamak istiyorsan, yakalamaya ihtiyacın yoksa (?:...) yazarsın. (?:paperback|ebook) alternation'ı sınırlar ama bir numara almaz. Bunun iki faydası var. Birincisi, sonradan pattern'in başına bir grup eklediğinde diğer grupların numarası kaymaz. İkincisi, engine yakalanan metni saklamak zorunda kalmaz; fark çoğu zaman küçük, ama bir log satırında yüzlerce kez tekrarlanan bir grupta ölçülebilir.

.NET'te RegexOptions.ExplicitCapture (inline hâli (?n)) bütün isimsiz parantezleri non-capturing yapar; yalnızca isimli gruplar yakalanır. Yukarıdaki numara karışıklığını kökten çözmenin bir yolu bu. Inline flag'leri dördüncü yazıda ayrıntılı göreceğiz.

Named Group Syntax'ı Flavor'lara Göre

İsimli grup için üç yazım var ve her flavor hepsini tanımıyor. (?<name>...) .NET, JavaScript ve PCRE2'de çalışır. (?'name'...) .NET ve PCRE2'de çalışır. (?P<name>...) Python'ın yazımıdır; PCRE2, Go'nun regexp paketi ve ripgrep'in varsayılan engine'i de bunu kabul eder. Python ise (?<name>...) yazımını tanımıyor; burada Python 3.14 ile denendi:

$ cat python/named-groups.py
import re

m = re.match(r"(?P<year>\d{4})-(\d{2})", "2026-11")
print(m.group(1), m.group(2), m.group("year"))

try:
    re.compile(r"(?<year>\d{4})")
except re.error as e:
    print("re.error:", e)
$ python3 python/named-groups.py
2026 11 2026
re.error: unknown extension ?<y at position 1

Aynı ismi iki kez kullanmak da flavor'a göre değişiyor. Tarih bazen 2026-11, bazen 11/2026 geliyorsa, iki alternatifte de yılı year adıyla yakalamak istersin. .NET buna her zaman izin veriyor; yukarıdaki son satır iki biçimde de 2026 döndü. JavaScript ES2025 ile buna izin verdi, ama yalnızca isimler farklı alternatiflerde duruyorsa: aynı alternatifte iki year yukarıdaki gibi SyntaxError veriyor. PCRE2 varsayılan olarak reddediyor, (?J) ile kabul ediyor:

$ pcre2test shell/named-groups.txt
PCRE2 version 10.49 2026-09-28 (8-bit)
/(?<year>\d{4})-(\d{2})/
2026-11
 0: 2026-11
 1: 2026
 2: 11

/(?P<year>\d{4})-(?'month'\d{2})/
2026-11
 0: 2026-11
 1: 2026
 2: 11

/(?<y>\d{4})-\d{2}|\d{2}\/(?<y>\d{4})/
Failed: error 143 at offset 30: two named subpatterns have the same name (PCRE2_DUPNAMES not set)
        here: ...{2}\/(?<y> |<--| \d{4})
11/2026

/(?J)(?<y>\d{4})-\d{2}|\d{2}\/(?<y>\d{4})/
11/2026
 0: 11/2026
 1: <unset>
 2: 2026

İlk iki blok PCRE2'nin numaralandırmasını da gösteriyor: year isimli olduğu hâlde 1 numara. Son blokta yıl 2 numaralı gruba düştü, 1 numaralı grup <unset>, yani bu eşleşmede hiç katılmadı. Bir grubun "boş string yakaladı" ile "hiç katılmadı" durumları farklıdır; .NET'te Group.Success, JavaScript'te undefined değeri bu farkı söyler.

Backreference: \1 ve \k<name>

Yakalanan metni pattern'in içinde tekrar kullanabilirsin. \1 "1 numaralı grubun yakaladığı metnin aynısı" demek, \k<name> de isimli grubun karşılığı. Bookstore'un katalog açıklamalarında yazım hatası olarak tekrar eden kelimeleri arayalım:

using System.Text.RegularExpressions;

foreach (string line in File.ReadLines("data/descriptions.txt"))
{
    // \k<word>, grubun yakaladığı metni birebir tekrar etmelidir
    foreach (Match m in Regex.Matches(line, @"\b(?<word>\w+) \k<word>\b", RegexOptions.IgnoreCase))
        Console.WriteLine($"{line[..13]} \"{m.Value}\" at {m.Index}");
}

// alternation: eşleşen ilk alternatif kazanır
Console.WriteLine(Regex.Match("BKS-2026-000123", "BKS|BKS-2026").Value);

// ^ yalnız "paperback"e, $ yalnız "ebook"a bağlanır
Console.WriteLine($"{Regex.IsMatch("paperback edition", "^paperback|ebook$")} {Regex.IsMatch("paperback edition", "^(?:paperback|ebook)$")}");
$ dotnet run dotnet/repeated-words.cs
9780441172719 "the the" at 50
9780547928227 "is is" at 28
BKS
True False
$ node js/repeated-words.mjs
9780441172719 "the the" at 50
9780547928227 "is is" at 28
BKS
true false
$ grep -nE '\b(\w+) \1\b' data/descriptions.txt
1:9780441172719|A desert planet, a noble family and the the spice that controls the universe.
3:9780547928227|Bilbo Baggins is is drawn into a quest for a dragon's treasure.
$ grep -noP '\b(?<w>\w+) \k<w>\b' data/descriptions.txt
1:the the
3:is is
$ rg '\b(\w+) \1\b' data/descriptions.txt
rg: regex parse error:
    (?:\b(\w+) \1\b)
               ^^
error: backreferences are not supported

Consider enabling PCRE2 with the --pcre2 flag, which can handle backreferences
and look-around.

\b'ler önemli: onlarsız this is içindeki is is de eşleşirdi, çünkü this kelimesinin sonu is. GNU grep -E backreference'ı destekliyor, ama kılavuzu bunu POSIX (Unix araçlarının uyduğu standart) tanımında olmayan, taşınabilir olmayan bir davranış olarak işaretliyor. ripgrep'in varsayılan engine'i ise backreference'ı hiç desteklemiyor ve -P ile PCRE2'ye geçmeni öneriyor. Bunun sebebi tasarım: backreference, engine'in eşleşme sırasında geçmişe bakmasını gerektirir ve lineer zamanda çalışma garantisiyle bağdaşmaz. Bu ayrımı beşinci yazıda RE2 (Google'ın backtracking yapmayan regex library'si) ailesiyle birlikte ele alacağız.

Backreference'ın bir sınırı da var: "aynı metin" demek, "aynı anlam" demek değil. that that İngilizcede bazen doğru bir cümledir; regex bunu bilemez. Bu tür aramalar bir aday listesi üretir, kararı bir insan verir.

Alternation ve Önceliği

| regex'teki en düşük öncelikli operatör: soldaki ve sağdaki bütün pattern'i ayırır. ^paperback|ebook$ "satır başında paperback ya da satır sonunda ebook" demek, "satırın tamamı paperback ya da ebook" değil. Yukarıdaki çıktıda paperback edition bu yüzden ilk pattern'den geçti. Alternation'ı sınırlamak için grup kullanırsın: ^(?:paperback|ebook)$.

Alternatifler arasındaki sıra da önemli. .NET, JavaScript ve PCRE2 alternatifleri soldan sağa dener ve tutan ilkinde durur; BKS|BKS-2026 bu yüzden BKS döndü. POSIX ise en uzun eşleşmeyi ister:

$ echo 'BKS-2026-000123' | grep -oE 'BKS|BKS-2026'
BKS-2026
$ echo 'BKS-2026-000123' | grep -oP 'BKS|BKS-2026'
BKS
$ echo 'BKS-2026-000123' | rg -o 'BKS|BKS-2026'
BKS
$ echo 'BKS-2026-000123' | sed -E 's/BKS|BKS-2026/[&]/'
[BKS-2026]-000123

grep -E ve sed aynı pattern'le daha uzun metni buldu. Aynı pattern'i grep -E'de deneyip .NET'e taşıdığında farklı sonuç almanın bir sebebi bu. Backtracking yapan engine'lerde kural pratik: daha uzun ya da daha özel alternatifi başa yaz, BKS-2026|BKS gibi. ripgrep backtracking yapmıyor ama Perl ile aynı "soldaki alternatif önce" kuralını uyguluyor.

Replace: $1, ${name} ve Araçlara Göre Syntax

Replacement string'i de kendi küçük dilini taşır ve bu dil araçtan araca değişir:

using System.Text.RegularExpressions;

string price = "Emma: 45";

// 1. grup ve ardından literal 0?
Console.WriteLine(Regex.Replace(price, @"(\d+)", "$10 TRY"));
Console.WriteLine(Regex.Replace(price, @"(\d+)", "${1}0 TRY"));
// $$ literal dolar işaretidir, $0 eşleşmenin tamamı, olmayan grup metin olarak kalır
Console.WriteLine(Regex.Replace(price, @"(\d+)", "$$$1"));
Console.WriteLine(Regex.Replace(price, @"(\d+)", "[$0]"));
Console.WriteLine(Regex.Replace(price, @"(\d+)", "$2"));
$ dotnet run dotnet/replace-syntax.cs
Emma: $10 TRY
Emma: 450 TRY
Emma: $45
Emma: [45]
Emma: $2
$ cat js/replace-syntax.mjs
const price = "Emma: 45";

console.log(price.replace(/(\d+)/, "$10 TRY"));
console.log(price.replace(/(\d+)/, "$010 TRY"));
console.log(price.replace(/(\d+)/, "$$$1"));
console.log(price.replace(/(\d+)/, "[$&]"));
console.log(price.replace(/(\d+)/, "$2"));
console.log(price.replace(/(?<amount>\d+)/, "$<amount>0 TRY"));
$ node js/replace-syntax.mjs
Emma: 450 TRY
Emma: 450 TRY
Emma: $45
Emma: [45]
Emma: $2
Emma: 450 TRY
$ echo 'Emma: 45' | sed -E 's/([0-9]+)/\10 TRY/'
Emma: 450 TRY
$ echo 'Emma: 45' | sed -E 's/([0-9]+)/[&]/'
Emma: [45]

Aynı $10 .NET'te olduğu gibi metin kaldı, JavaScript'te "1. grup ve 0" oldu. .NET önce 10 numaralı grubu arıyor, bulamayınca ifadeyi literal bırakıyor; JavaScript ise iki haneli numara geçersizse tek haneye düşüyor. Belirsizliği iki tarafta da ortadan kaldırmanın yolu farklı: .NET'te ${1}0, JavaScript'te iki haneli $010 ya da isimli grupla $<amount>0. sed yalnızca \1-\9'u tanıdığı için \10 orada her zaman "1. grup ve 0" demek. Aşağıdaki tabloya editör olarak Visual Studio Code'u (VS Code) da ekledim.

İş.NET ve Visual StudioJavaScriptsed (GNU)VS Code
1. grup$1$1\1$1
İsimli grup${name}$<name>yok-
Eşleşmenin tamamı$0$&&-
1. grup ve ardından 0${1}0$010\10-
Literal dolar$$$$$-
Büyük/küçük harfMatchEvaluatorcallback\U \L \u \l \E\U \L \u \l

Visual Studio'nun find/replace'i doğrudan .NET regex'ini kullanıyor, bu yüzden .NET ile aynı sütunda. VS Code sütununda yalnızca dokümanında geçenleri yazdım: $n ile grup ve \u, \U, \l, \L ile harf dönüşümü. Bu dört modifier'ın kullanımını, IDE'de repo genelinde toplu değişiklikle birlikte altıncı yazıda göreceğiz.

Bookstore: "Herbert, Frank" → "Frank Herbert"

Bookstore'a bir yayıncıdan gelen katalog dosyasında yazar adları kütüphane düzeninde, soyadı önde yazılmış. Vitrinde ad önde görünmeli:

$ cat data/authors.csv
isbn,title,author
9780441172719,Dune,"Herbert, Frank"
9780141439587,Emma,"Austen, Jane"
9780547928210,"Fellowship of the Ring, The","Tolkien, J. R. R."
9780141439549,Middlemarch,George Eliot

Dosyada bir tuzak var: kitap adı da kütüphane düzeninde ve virgüllü. İlk denemede "tırnaklı ve virgüllü her alan"ı çeviriyoruz, ikincisinde yalnızca satırın sonundaki alanı:

using System.Text.RegularExpressions;

string[] lines = File.ReadAllLines("data/authors.csv");

// ilk deneme: tırnaklı her "Soyad, Ad" çifti
Regex anyPair = new(@"""([^"",]+), ([^""]+)""");
// yalnız son kolon: tırnaklı alan satırı bitirmeli
Regex author = new(@"""(?<last>[^"",]+), (?<first>[^""]+)""$");

foreach (string line in lines)
    Console.WriteLine(anyPair.Replace(line, "\"$2 $1\""));
Console.WriteLine();

foreach (string line in lines)
    Console.WriteLine(author.Replace(line, "\"${first} ${last}\""));
Console.WriteLine();

// yeni metin kod gerektiriyorsa MatchEvaluator
foreach (string line in lines)
    Console.WriteLine(author.Replace(line, m => $"\"{m.Groups["last"].Value.ToUpperInvariant()}, {m.Groups["first"].Value}\""));
$ dotnet run dotnet/authors.cs
isbn,title,author
9780441172719,Dune,"Frank Herbert"
9780141439587,Emma,"Jane Austen"
9780547928210,"The Fellowship of the Ring","J. R. R. Tolkien"
9780141439549,Middlemarch,George Eliot

isbn,title,author
9780441172719,Dune,"Frank Herbert"
9780141439587,Emma,"Jane Austen"
9780547928210,"Fellowship of the Ring, The","J. R. R. Tolkien"
9780141439549,Middlemarch,George Eliot

isbn,title,author
9780441172719,Dune,"HERBERT, Frank"
9780141439587,Emma,"AUSTEN, Jane"
9780547928210,"Fellowship of the Ring, The","TOLKIEN, J. R. R."
9780141439549,Middlemarch,George Eliot

İlk denemede kitap adı da çevrildi; sonuç okunaklı görünüyor, ama istemediğimiz bir kolonu değiştirdik. $ ile alanı satırın sonuna bağlayınca yalnızca yazar kolonu değişti. İsimli gruplar replacement'ı "$2 $1"'den daha okunur yapıyor ve gruba bir şey eklendiğinde kaymıyor. Üçüncü blok MatchEvaluator ile soyadını büyük harfe çevirdi; replacement string'inin dili bunu yapamıyor, kod gerekiyor. George Eliot tırnaksız ve virgülsüz olduğu için hiçbir pattern'e takılmadı.

JavaScript'te isimli grup $<name> ile yazılıyor, callback'te gruplar son argümanda geliyor:

import { readFileSync } from "node:fs";

const csv = readFileSync("data/authors.csv", "utf8");
const author = /"(?<last>[^",]+), (?<first>[^"]+)"$/gm;

console.log(csv.replace(author, '"$<first> $<last>"'));
console.log(csv.replace(author, (...args) => {
  const { last, first } = args.at(-1);
  return `"${last.toUpperCase()}, ${first}"`;
}));

Komut satırında GNU sed, \U ile büyük harfe çevirmeyi replacement'ın içinde yapabiliyor:

$ cat shell/authors.sh
#!/usr/bin/env bash
# "Herbert, Frank" -> "Frank Herbert" (yalnız son kolon)
sed -E 's/"([^",]+), ([^"]+)"$/"\2 \1"/' data/authors.csv
echo
# GNU sed: \U, \E'ye kadar büyük harfe çevirir
sed -E 's/"([^",]+), ([^"]+)"$/"\U\1\E, \2"/' data/authors.csv
$ bash shell/authors.sh
isbn,title,author
9780441172719,Dune,"Frank Herbert"
9780141439587,Emma,"Jane Austen"
9780547928210,"Fellowship of the Ring, The","J. R. R. Tolkien"
9780141439549,Middlemarch,George Eliot

isbn,title,author
9780441172719,Dune,"HERBERT, Frank"
9780141439587,Emma,"AUSTEN, Jane"
9780547928210,"Fellowship of the Ring, The","TOLKIEN, J. R. R."
9780141439549,Middlemarch,George Eliot

Bu, tek seferlik bir veri temizliği için yeterli. Dosya düzenli olarak geliyorsa ve içinde tırnaklı, satır sonu içeren alanlar olabiliyorsa, CSV (virgülle ayrılmış değer formatı) parser'ı kullanmak daha güvenli; .NET'te I/O İşlemleri serisinin CSV yazısı bunun yollarını anlatıyor.

Üretimde Dikkat

Replacement string'i kullanıcıdan geliyorsa onun da kaçışa ihtiyacı var. Bookstore'un yönetim panelinde mağaza çalışanı bir kampanya metni yazıyor ve kod bunu bir şablondaki {discount} yerine koyuyor:

using System.Text.RegularExpressions;

string banner = "Dune: {discount}";
string discount = "$0.99 off";   // mağaza çalışanının yazdığı metin

Console.WriteLine(Regex.Replace(banner, @"\{discount\}", discount));
Console.WriteLine(Regex.Replace(banner, @"\{discount\}", discount.Replace("$", "$$")));
Console.WriteLine(Regex.Replace(banner, @"\{discount\}", _ => discount));
$ dotnet run dotnet/dollar.cs
Dune: {discount}.99 off
Dune: $0.99 off
Dune: $0.99 off
$ node js/dollar.mjs
Dune: {discount} off
Dune: $& off
Dune: $& off
Dune: $0.99 off

.NET $0'ı "eşleşmenin tamamı" olarak okudu ve {discount}'u geri yazdı. JavaScript dosyasında aynı sorunu $& ile gösterdik; son satır da JavaScript'te $0'ın özel olmadığını gösteriyor. İki güvenli yol var: dolar işaretlerini $$'a çevirmek ya da replacement yerine her zaman sabit metin döndüren bir MatchEvaluator veya callback vermek. Hiç regex gerekmiyorsa string.Replace en basiti.

Satır yapısına bağlı pattern'lerde satır sonlarına dikkat et. Windows'ta CRLF'li (her satır sonunda \r\n) bir dosyayı File.ReadAllText ile tek string olarak okuyup RegexOptions.Multiline ile "$ arasaydık, .NET hiçbir satır bulmazdı; çünkü .NET'in $'ı yalnızca \n'den önce eşleşiyor, \r'den önce değil. JavaScript'in m flag'li $'ı ise \r'yi de satır sonu sayıyor. Bu yüzden .NET örneğinde dosyayı ReadAllLines ile satır satır okuduk; konunun ayrıntısı altıncı yazıda.

Grup numaralarını kodun derinliklerine dağıtma. m.Groups[3] yazan bir satır, pattern'e bir grup eklendiği gün sessizce yanlış alanı okur; m.Groups["status"] okumaz.

Sık Düşülen Tuzaklar

İsimli ve isimsiz grupları karıştırmak

.NET'te isimli gruplar sona numaralanır, JavaScript ve PCRE2'de sırayla. Aynı pattern iki tarafta farklı $1 üretir. Ya hepsine isim ver ya da (?n) ile isimsizleri kapat.

İç içe gruplarda numaranın kayması

Pattern'in başına (https?):// gibi bir grup eklediğinde eski $1 artık $2'dir. Yalnızca gruplamak için (?:...) kullan.

Replacement'ta dolar işaretini unutmak

Fiyat, kampanya metni ya da kullanıcıdan gelen herhangi bir metin $ içerebilir. $$'a çevir ya da evaluator/callback kullan.

$10 belirsizliği

.NET'te metin kalır, JavaScript'te "1. grup ve 0" olur. .NET'te ${1}0, JavaScript'te $010 ya da isimli grup yaz.

Alternation'ın kapsamı

^a|b$ iki ayrı pattern'dir. Anchor'ları ve alternation'ı bir arada kullanırken alternation'ı grupla sınırla.

Küçük Bir Deneme

★☆☆ data/authors.csv'deki ISBN'leri (kitapların uluslararası standart numarası) 978-0-441-17271-9 gibi tireli yaz. Yayıncı parçası uzunluğu değiştiği için gerçek tireleme ISBN ajansının aralık tablosuna bağlı; bu alıştırmada yalnızca Dune'un 3-1-3-5-1 düzenini kullan ve .NET'te isimli gruplarla, sed'de \1-\5 ile yap.

★★☆ Dosyaya Emma'nın altına Austen'ın Pride and Prejudice'ini ekle (9780141439518). Aynı yazarın art arda gelen satırlarını backreference ile bul. İpucu: -z ile grep'e dosyayı tek kayıt olarak ver ve satır sonunu pattern'e yaz.

★★★ (?<year>\d{4})-(\d{2}) pattern'ini ve $2/$1 replacement'ını hem .NET'te hem JavaScript'te 11/2026 üretecek şekilde değiştir; tek bir pattern ve tek bir replacement string'i kullan.

Bu yazıda grupların nasıl numaralandığını, isimli grupların flavor'lara göre nasıl yazıldığını ve replace syntax'ının araçtan araca değiştiğini gördük. Bir sonraki yazıda karakter tüketmeden kontrol yapan yapılara geçiyoruz: lookahead ve lookbehind, inline flag'ler, JavaScript'in v flag'i, \p{L} gibi Unicode property'leri ve Türkçe İ/ı ile büyük/küçük harf duyarsız arama.