Przejdź do treści
Toova
Wszystkie narzędzia

10 Sztuczek Regex, Które Powinien Znać Każdy Programista

Toova

Wyrażenia regularne to jedno z tych narzędzi, które na początku wydają się nie do przebicia, a potem nagle wszystko wskakuje na swoje miejsce. Gdy raz zaskoczą, zaczynasz widzieć je wszędzie: walidacja wejścia, parsowanie logów, pipeline'y typu search-and-replace, routing URL. Ale większość programistów używa tylko garstki funkcji - klasy znaków, kwantyfikatory, kotwice - i pomija resztę specyfikacji.

Ten przewodnik pokrywa dziesięć funkcji regex, które wykraczają poza podstawy. Każda rozwiązuje prawdziwy problem, którego prostsze wzorce nie obsłużą czysto. Wszystkie przykłady używają składni JavaScript, która jest również poprawna dla dowolnego środowiska zgodnego z ECMAScript.

Możesz przetestować każdy wzorzec z tego artykułu używając narzędzia Toova Regex Tester bez pisania ani jednej linii kodu setupowego.

1. Lookaheady: Dopasowanie Bez Konsumowania

Lookahead potwierdza, że wzorzec musi (lub nie może) następować po bieżącej pozycji, bez przesuwania silnika dopasowania poza te znaki. Dopasowany tekst nie zawiera tego, co sprawdza lookahead.

Składnia pozytywnego lookaheadu: (?=...)

// Pozytywny lookahead: dopasuj "foo" tylko gdy następuje "bar"
const re1 = /foo(?=bar)/;
re1.test('foobar'); // true
re1.test('foobaz'); // false

Składnia negatywnego lookaheadu: (?!...)

// Negatywny lookahead: dopasuj "foo" gdy NIE następuje "bar"
const re2 = /foo(?!bar)/;
re2.test('foobaz'); // true
re2.test('foobar'); // false

Praktyczne zastosowanie: dopasuj liczbę ceny tylko wtedy, gdy następuje po niej symbol waluty, bez uwzględniania symbolu w przechwyconej wartości. Albo zwaliduj, że hasło zawiera co najmniej jedną cyfrę używając (?=.*\d) bez określania, gdzie cyfra musi się pojawić.

Lookaheady mają zerową szerokość - nie konsumują znaków. Możesz nakładać wiele lookaheadów w tej samej pozycji, aby wymusić kilka niezależnych warunków jednocześnie.

2. Lookbehindy: Sprawdź, Co Było Wcześniej

Lookbehind jest lustrzanym odbiciem lookaheadu: sprawdza tekst poprzedzający bieżącą pozycję bez włączania go do dopasowania.

Składnia pozytywnego lookbehindu: (?<=...)

// Pozytywny lookbehind: dopasuj "bar" tylko gdy poprzedza je "foo"
const re3 = /(?<=foo)bar/;
re3.test('foobar'); // true
re3.test('bazbar'); // false

Składnia negatywnego lookbehindu: (?<!...)

// Negatywny lookbehind: dopasuj "bar" gdy NIE poprzedza go "foo"
const re4 = /(?<!foo)bar/;
re4.test('bazbar'); // true
re4.test('foobar'); // false

Lookbehindy wylądowały w ECMAScript 2018 i są obsługiwane we wszystkich nowoczesnych przeglądarkach oraz Node.js 10+. Typowy przypadek użycia: wydobycie wartości z pary klucz-wartość typu name=Alice przez dopasowanie wszystkiego po name= bez uwzględniania klucza w dopasowaniu.

Uwaga: w przeciwieństwie do lookaheadów, wyrażenia lookbehind w JavaScript nie mogą zawierać wzorców o zmiennej długości - wyrażenie lookbehind musi mieć stałą lub ograniczoną maksymalną długość.

3. Nazwane Grupy Przechwytywania: Samodokumentujące się Wzorce

Standardowe grupy przechwytywania są referencowane przez numer: $1, $2 i tak dalej. Gdy dodajesz lub usuwasz grupę, każda kolejna referencja się łamie. Nazwane grupy rozwiązują to, pozwalając przypisać etykietę każdej grupie.

const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const m = '2026-05-10'.match(dateRe);
console.log(m.groups.year);  // "2026"
console.log(m.groups.month); // "05"
console.log(m.groups.day);   // "10"

Nazwane grupy są również dostępne w ciągach zastępujących poprzez $<name>:

// Back-reference do nazwanej grupy w wzorcu
const quoteRe = /(?<q>['"]).*?\k<q>/;
quoteRe.test('"hello"'); // true
quoteRe.test('"hello''); // false

Nazwa grupy musi być prawidłowym identyfikatorem JavaScript. Używaj opisowych nazw, które odzwierciedlają to, co grupa przechwytuje - year, port, protocol - a twoje wzorce staną się niemal samodokumentujące. Możesz również użyć \k<name> wewnątrz samego wzorca, aby zrobić back-reference do nazwanej grupy, jak pokazano powyżej.

4. Kwantyfikatory Nie-Zachłanne: Dopasuj Minimum

Domyślnie kwantyfikatory (*, +, ?) są zachłanne - dopasowują tyle znaków, ile to możliwe. Dodanie ? po kwantyfikatorze sprawia, że staje się nie-zachłanny (zwany też leniwym lub niechętnym), dopasowując tak mało znaków, jak to możliwe.

const html = '<a>click</a>';

// Zachłanny (domyślny) - dopasowuje najdłuższy możliwy ciąg
/<.+>/.exec(html)?.[0]; // '<a>click</a>'

// Nie-zachłanny - dopasowuje najkrótszy możliwy ciąg
/<.+?>/.exec(html)?.[0]; // '<a>'

To ma znaczenie przy parsowaniu HTML, XML lub dowolnego formatu, gdzie ten sam ogranicznik może pojawiać się wiele razy. Wersja zachłanna pochłania wszystko od pierwszego otwarcia do ostatniego zamknięcia w całym ciągu. Wersja nie-zachłanna zatrzymuje się przy pierwszym poprawnym zamknięciu.

To samo dotyczy +? (jeden lub więcej, leniwy) oraz ?? (zero lub jeden, leniwy). Kwantyfikatory nie-zachłanne nie zmieniają tego, co może być dopasowane - zmieniają, które poprawne dopasowanie zostanie wybrane, gdy istnieje wiele opcji.

5. Unikanie Katastrofalnego Powrotu

Backtracking to sposób, w jaki silniki regex odzyskują się po nieudanej próbie dopasowania - próbują innej ścieżki przez wzorzec. W większości przypadków jest to niewidoczne i szybkie. Ale pewne wzorce mogą sprawić, że silnik eksploruje wykładniczo rosnącą liczbę ścieżek, kładąc proces Node.js na łopatki nawet dla skromnego ciągu wejściowego.

Klasycznym niebezpiecznym wzorcem są zagnieżdżone kwantyfikatory typu (a+)+ stosowane do ciągu typu aaaaab. Silnik próbuje każdego możliwego sposobu podziału znaków a między grupę wewnętrzną i zewnętrzną, zanim stwierdzi, że nie ma dopasowania.

Grupy atomowe ((?>...)) zapobiegają temu, mówiąc silnikowi, żeby nie wracał do grupy po jej dopasowaniu. JavaScript nie obsługuje grup atomowych natywnie, ale można emulować zachowanie zaborcze za pomocą lookaheadu:

// Bez grupy atomowej - silnik powraca do (\d+)
// Z grupą atomową - gdy (\d+) zostanie dopasowane, powrót nie jest dozwolony
// JavaScript natywnie nie obsługuje grup atomowych,
// ale można je emulować sztuczką z lookaheadem:
const re5 = /(?=(\d+))\1(?!\d)/; // emulacja zaborczego \d++

Bezpieczniejsza zasada: unikaj kwantyfikatorów bezpośrednio zagnieżdżonych wewnątrz innych kwantyfikatorów, chyba że masz konkretny powód. Przepisz wzorce, aby były bardziej precyzyjne co do tego, co dopasowują. Możesz również użyć narzędzia Text Diff, aby porównać wynik dwóch równoważnych wzorców obok siebie podczas refaktoryzacji.

6. Operacje na Zbiorach Klas Znaków (Flaga Unicode v)

ECMAScript 2024 wprowadził flagę v, która umożliwia operacje na zbiorach wewnątrz klas znaków. Pozwala to wyrazić "wszystkie litery z wyjątkiem samogłosek" lub "wielkie litery, które są również ASCII" jako czystą definicję klasy zamiast nieporęcznej alternatywy.

// Odejmowanie klas znaków POSIX nie istnieje w JS,
// ale tryb zbiorów Unicode (flaga `v`) dodaje operacje zbiorów:
const lettersNoVowels = /[a-z--[aeiou]]/v;
lettersNoVowels.test('b'); // true
lettersNoVowels.test('e'); // false

Flaga v obsługuje trzy operacje wewnątrz klas znaków:

  • Odejmowanie: [A--B] - znaki w A, ale nie w B
  • Część wspólna: [A&&B] - znaki w A i w B
  • Suma: [AB] - znaki w A lub B (tak samo jak standardowe klasy znaków)

Node.js 20+ i wszystkie zawsze aktualne przeglądarki obsługują flagę v. Jest ona nadzbiorem flagi u - nie łącz obu; używaj samej v, gdy potrzebujesz jej funkcji.

7. Granice Słów: Dopasowanie Całych Wyrazów

Kotwica \b dopasowuje pozycję między znakiem słowotwórczym (\w) a znakiem niesłowotwórczym. Nie konsumuje znaków - tylko potwierdza pozycję. Jej odwrotność \B dopasowuje dowolną pozycję, która nie jest granicą słowa.

const sentence = 'cat concatenate';

// Bez \b - "cat" znalezione też wewnątrz "concatenate"
/cat/g.exec(sentence); // dopasowuje "cat" w "cat" ORAZ w "concatenate"

// Z \b - tylko całe słowo "cat"
/\bcat\b/g.exec(sentence); // dopasowuje tylko samodzielne "cat"
// \B jest odwrotnością: dopasowuje wewnątrz słowa, nie na granicy
/\Bcat\B/.test('concatenate'); // true - "cat" jest wewnątrz słowa

Granice słów są niezbędne podczas wyszukiwania identyfikatorów w kodzie lub prozie. Bez nich szukanie zmiennej o nazwie id trafiłoby również w indexOf, invalid i grid. Używaj \bterm\b, aby ograniczyć dopasowania do samodzielnych wystąpień.

Jedno ważne zastrzeżenie: \b używa definicji znaku słowotwórczego z JavaScript ([a-zA-Z0-9_]). Znaki z akcentami i litery nielacińskie są traktowane jako znaki niesłowotwórcze. Dla granic słów świadomych Unicode, połącz flagę v z klasami właściwości Unicode.

8. Tryb Wieloliniowy: Kotwice Per Linia

Domyślnie ^ dopasowuje tylko sam początek ciągu, a $ dopasowuje tylko sam koniec. Flaga m (multiline) to zmienia: ^ dopasowuje początek każdej linii, a $ dopasowuje koniec każdej linii.

const text = 'line one\nline two\nline three';

// Bez flagi m - ^ dopasowuje tylko początek całego stringa
/^line/.test(text); // true (tylko pierwsza linia)

// Z flagą m - ^ dopasowuje początek KAŻDEJ linii
const matches = text.match(/^line/gm);
console.log(matches); // ['line', 'line', 'line']

Jest to niezastąpione przy przetwarzaniu tekstu wieloliniowego, takiego jak pliki logów, pliki konfiguracyjne czy kod. Typowe zastosowania to wydobywanie linii zaczynających się od słowa kluczowego, zastępowanie tokenów końca linii lub walidacja, że każda linia w bloku pasuje do wzorca.

Nie myl flagi m z flagą s (dotAll). Flaga s sprawia, że . dopasowuje również znaki nowej linii. Flaga m w ogóle nie wpływa na . - jedynie na zachowanie ^ i $.

9. Znaki Ucieczki Właściwości Unicode: Dopasowanie Znaków Międzynarodowych

Flaga u włącza znaki ucieczki właściwości Unicode, które pozwalają dopasowywać znaki na podstawie ich kategorii Unicode, systemu pisma lub innej właściwości. To poprawny sposób dopasowywania liter, cyfr czy interpunkcji we wszystkich systemach pisma używanych przez ludzi - nie tylko ASCII.

// Flaga u włącza znaki ucieczki właściwości Unicode
const letters = /\p{L}+/u;
letters.test('Héllo');   // true
letters.test('你好');    // true
letters.test('12345');   // false

// Dopasuj tylko wielkie litery we wszystkich systemach pisma
const upper = /\p{Lu}+/u;
upper.test('ABC');  // true
upper.test('abc');  // false
// Dopasuj emoji (kategoria ogólna Unicode: Symbol, Other)
const emoji = /\p{So}/u;
emoji.test('🚀'); // true

Najczęściej używane właściwości Unicode to:

  • \p{L} - dowolna litera (wszystkie systemy pisma)
  • \p{Lu} - wielkie litery
  • \p{Ll} - małe litery
  • \p{N} - dowolna liczba
  • \p{Nd} - cyfry dziesiętne
  • \p{P} - interpunkcja
  • \p{Script=Latin} - znaki pisma łacińskiego
  • \p{Emoji} - znaki emoji

Użyj \P{...} (wielkie P), aby zanegować - dopasowując wszystko, co nie ma określonej właściwości. Pełna lista obsługiwanych właściwości Unicode i ich wartości znajduje się w dokumentacji MDN.

10. Wzorce Czytelne Poprzez Składanie Stringów

Wiele wariantów regex (Python, Ruby, .NET, PCRE) obsługuje tryb verbose lub rozszerzony (flaga x), który pozwala na białe znaki i komentarze wewnątrz wzorców. JavaScript nie ma tej flagi - flaga x nie jest poprawna w ECMAScript.

Standardowe obejście to składanie wzorców z nazwanych stałych tekstowych i łączenie ich z new RegExp():

// JavaScript nie ma natywnej flagi x,
// ale można budować czytelne wzorce jako stałe tekstowe:
const YEAR  = '(?<year>\\d{4})';
const SEP   = '-';
const MONTH = '(?<month>\\d{2})';
const DAY   = '(?<day>\\d{2})';
const datePattern = new RegExp(YEAR + SEP + MONTH + SEP + DAY);

Każda stała opisuje, co dopasowuje, a finalny wzorzec czyta się jak zdanie. To podejście ułatwia również komponowanie współdzielonych podwzorców w wielu definicjach regex w bazie kodu oraz testowanie jednostkowe każdego komponentu niezależnie.

Dla mniej złożonych wzorców, trzymanie całego regex w jednej linii z komentarzami inline w pobliskim bloku komentarza często wystarczy. Cel to zapewnienie, że następny programista (w tym przyszły ty) zrozumie wzorzec bez przepuszczania go przez dekoder.

Wszystko Razem

Te dziesięć technik pokrywa dużą część powierzchni problemu "dlaczego ten regex zawodzi w przypadkach brzegowych?". Lookaheady i lookbehindy pozwalają potwierdzać kontekst bez jego konsumowania. Nazwane grupy utrzymują wzorce czytelne podczas refaktoryzacji. Kwantyfikatory nie-zachłanne zapobiegają przypadkowemu nadmiernemu dopasowaniu. Znaki ucieczki właściwości Unicode obsługują wejście wykraczające poza ASCII.

Najlepszy sposób budowania biegłości to eksperymentowanie z prawdziwymi wzorcami na prawdziwych danych. Użyj Regex Testera, aby szybko iterować. Gdy twój wzorzec produkuje wynik wymagający porównania lub czyszczenia, narzędzie Text Diff pokaże dokładnie, co zmieniło się między uruchomieniami. A jeśli twoje wyrażenie regularne parsuje JSON, narzędzie JSON Formatter pozwala zbadać ustrukturyzowany wynik bez opuszczania przeglądarki.

Aby uzyskać kompleksową referencję wszystkich składni i flag regex w JavaScript, MDN Regex Cheatsheet to najlepsza pojedyncza strona do zakładek. Do interaktywnego debugowania wzorców z pełną wizualizacją dopasowań, regex101.com obsługuje tryb JavaScript z wbudowanym wyjaśnieniem każdego komponentu wzorca.