Kompiluj raz, potem dopasowuj
Zapisuj wzorce jako surowe literały stringów w backtickach. W zwykłym stringu w cudzysłowach każdy ukośnik wsteczny trzeba podwoić ("\\d+"), co szybko staje się nieczytelne.
regexp.MustCompile wywołuje panic, jeśli wzorzec jest niepoprawny, i właśnie tego chcesz dla wzorca w kodzie źródłowym: literówka ujawnia się od razu przy starcie, a nie przy pierwszym żądaniu. Dla wzorców od użytkowników lub z konfiguracji użyj regexp.Compile i obsłuż błąd:
re, err := regexp.Compile(userPattern)
if err != nil {
return fmt.Errorf("bad pattern: %w", err)
}
Kompiluj raz. Kompilacja jest dużo droższa niż dopasowanie. regexp.MustCompile wewnątrz funkcji wywoływanej dla każdego żądania lub każdej linii powtarza tę pracę za każdym razem. Umieść skompilowany *regexp.Regexp w var na poziomie pakietu albo w polu struktury. Skompilowany regexp można bezpiecznie używać z wielu goroutine jednocześnie.
regexp.MatchString(pattern, s) kompiluje i dopasowuje w jednym wywołaniu. Nadaje się do jednorazowego sprawdzenia, ale w pętli marnuje czas.
Wyszukiwanie dopasowań
Nazwy metod układają się według schematu: Find + All? + String? + Submatch? + Index?.
| Część | Znaczenie |
|---|---|
All | każde nienakładające się dopasowanie; przyjmuje limit n (-1 oznacza wszystkie) |
String | działa na string; bez niej metoda przyjmuje i zwraca []byte |
Submatch | zwraca też grupy przechwytujące |
Index | zwraca przesunięcia w bajtach zamiast tekstu |
FindString zwraca "", gdy nic nie pasuje, czego nie da się odróżnić od dopasowania pustego stringa. Gdy wzorzec może dopasować pusty tekst, użyj FindStringIndex (zwraca nil, gdy brak dopasowania) albo najpierw MatchString. Warianty All zwracają nil, gdy nic nie pasuje.
Grupy przechwytujące
Nawiasy przechwytują. FindStringSubmatch zwraca całe dopasowanie pod indeksem 0, a potem po jednym elemencie na każdą grupę:
Zawsze sprawdź nil przed indeksowaniem wyniku; gdy brak dopasowania, m[1] wywołuje panic.
(?:...) grupuje bez przechwytywania, do alternatywy lub powtórzeń: (?:ab)+. Grupy nazwane zapisuje się jako (?P<name>...), a od Go 1.22 także (?<name>...).
Zamiana
Klasyczna pułapka z odwołaniami do grup: "$1x" jest czytane jako grupa o nazwie 1x, która nie istnieje, i rozwija się do pustego stringa. Napisz "${1}x".
Dla stałego tekstu strings.ReplaceAll, strings.Contains i strings.Split są prostsze i szybsze niż regexp. Sięgaj po regexp, gdy szukany tekst ma określony kształt, a nie stałą wartość.
Ściągawka ze składni
Go używa składni RE2, która obejmuje znane funkcje w stylu Perla:
| Składnia | Dopasowuje |
|---|---|
. | dowolny znak poza znakiem nowej linii (z (?s) także nową linię) |
\d \w \s | cyfrę, znak słowa [0-9A-Za-z_], biały znak (tylko ASCII) |
\D \W \S | zaprzeczenia powyższych |
[abc] [^abc] [a-z] | klasy znaków |
\pL \p{Greek} | klasy Unicode: dowolna litera, dowolny znak grecki |
* + ? {n,m} | powtórzenie, zachłanne |
*? +? ?? | powtórzenie, leniwe |
^ $ | początek i koniec tekstu (linii, z (?m)) |
\b | granica słowa (ASCII) |
a|b | alternatywa |
(?i) | bez rozróżniania wielkości liter od tego miejsca do końca otaczającej grupy; zwykle zapisywane na początku wzorca |
\d i \w dopasowują tylko ASCII. Dla „dowolnej litery w dowolnym języku” użyj \pL, a dla dowolnej cyfry Unicode \p{Nd}. regexp.QuoteMeta(s) escapuje każdy znak specjalny w s, co jest potrzebne, gdy budujesz wzorzec z danych od użytkownika.
Czego RE2 nie potrafi
Regexp w Go gwarantuje, że dopasowanie zajmuje czas liniowy względem długości wejścia. Funkcje, które złamałyby tę gwarancję, nie są obsługiwane:
- Brak lookahead i lookbehind:
(?=...),(?!...),(?<=...),(?<!...)dają błąd kompilacji. Ponieważ Go 1.22 zaczęło akceptować(?<name>...)dla grup nazwanych, lookbehind kończy się teraz mylącym komunikateminvalid named capture, co pokazuje program poniżej. - Brak odwołań wstecznych:
(\w)\1do dopasowania podwojonej litery nie jest możliwe. - Brak kwantyfikatorów zaborczych i grup atomowych.
Obejście jest prawie zawsze takie samo: przechwyć trochę więcej, niż potrzebujesz, a potem przefiltruj lub przytnij wynik w Go. W zamian regexp w Go nie da się wpędzić w katastrofalny backtracking. Wzorzec taki jak (a+)+$, który potrafi zamrozić silnik PCRE na krótkim wejściu, działa tu w czasie liniowym, co ma znaczenie, gdy wzorzec lub dane pochodzą z zewnątrz.
Typowe błędy
- Kompilowanie w pętli lub w handlerze. Skompiluj raz do zmiennej na poziomie pakietu.
- Wzorce w cudzysłowach.
"\d"nie jest nawet poprawnym stringiem w Go; użyj backticków. - Brak kotwic przy walidacji.
\d{5}pasuje wewnątrz"abc123456xyz". Użyj^\d{5}$. - Indeksowanie wyniku submatcha bez sprawdzenia
nil. - Oczekiwanie, że
\wlub\bzrozumieją tekst spoza ASCII. Użyj klas Unicode, takich jak\pL. - Używanie regexp dla stałych stringów. Pakiet
stringsjest czytelniejszy i szybszy.
Najczęściej zadawane pytania
Jak sprawdzić, czy string pasuje do wyrażenia regularnego w Go?
Skompiluj wzorzec raz przez regexp.MustCompile, zapisując go jako surowy string w backtickach, żeby ukośniki wsteczne nie wymagały escapowania, a potem wywołaj re.MatchString(s). Bez kotwic (^ i $) MatchString zwraca true, jeśli wzorzec pasuje w dowolnym miejscu stringa.
Czy regexp w Go obsługuje lookahead i lookbehind?
Nie. Pakiet regexp w Go używa składni RE2, która nie ma lookahead, lookbehind ani odwołań wstecznych. W zamian dopasowanie działa w czasie liniowym względem rozmiaru danych, więc złośliwy wzorzec lub wejście nie zawiesi programu. Zamiast tego użyj grup przechwytujących i odrobiny kodu w Go albo podziel sprawdzanie na dwa wyrażenia regularne.
Czym różni się regexp.Compile od regexp.MustCompile?
Compile zwraca skompilowany wzorzec i błąd. MustCompile przy niepoprawnym wzorcu wywołuje panic. Używaj MustCompile dla wzorców zapisanych w kodzie źródłowym, zwykle w zmiennej na poziomie pakietu, żeby literówka ujawniła się przy starcie. Compile stosuj dla wzorców pochodzących od użytkowników lub z plików konfiguracyjnych.
Jak pobrać grupy przechwytujące z wyrażenia regularnego w Go?
Użyj FindStringSubmatch, która zwraca slice, gdzie indeks 0 to całe dopasowanie, a indeksy 1, 2, ... to grupy, albo nil, gdy nic nie pasuje. Dla wszystkich dopasowań użyj FindAllStringSubmatch(s, -1). Grupy nazwane (?P<year>\d{4}) można odnaleźć przez re.SubexpIndex("year").