Za pomocą wyrażeń regularnych (ang. regular expressions albo w skrócie regexp) można opisywać pewne skomplikowane wzorce wyszukiwania na przykład treści dokumentów.
Przykładowo, za pomocą wzorca
[a-zA-Z0-9._-]+@[a-zA-Z0-9-]+\.[a-zA-Z]{2,4}
można próbować sprawdzać poprawność wpisanego w formularz adresu e-mail
[Piszę „próbować sprawdzać”, bo w istocie
sprawdzenie poprawności adresu email to wyzwanie, że ho-ho. Format adresu
email regulowany jest RFC 822
i choć z pozoru opis wydaje się prosty, to wyrażenie regularne, które
sprawdza poprawność tegoż proste nie
jest] . Część pierwsza wzorca, [a-zA-Z0-9._-]+
„wyłapie” pierwszy człon, adresu,
[a-zA-Z0-9-]+
nazwę domeny, a \.[a-zA-Z]{2,4}
domenę najwyższczego rzędu, czyli .pl, com lub .info. Już ten
przykład pokazuje — mam nadzieję — jak bardzo wyrafinowanym narzędziem są
wyrażenia regularne. I uwierz mi, że ich znajomość — wcześniej czy
później — zaoszczędzi Ci, drogi czytelniku, wielu godzin żmudnej pracy.
Nie ma nic bardziej deprymującego od konieczności dokonania jednakowych,
powtarzalnych czynności na dużym pliku tekstowym. Wyrażenia regularne
umożliwiają czynności tych zautomatyzowanie.
Uwaga porządkowa: jeśli nie będzie prowadziło to do niejednoznaczności — a nie powinno — to miejscami, zamiast „wyrażenia regularne”, będę pisał WR.
Wszystkie porządne języki programowania mają zaimplementowany mechanizm wsparcia dla WR. Dotyczy to Perla, PHP, JavaScriptu, Javy, Ruby’ego, Pythona i zapewne wielu innych języków, których nie sposób wszystkich utaj wymienić.
Podobnie, wiele narzędzi — ze szczególnym wskazaniem na narzędzie systemów typu POSIX — wspiera lub nawet jest przeznaczonych wyłącznie do operacji na wyrażenia regularnych. Mowa tu przede wszystkim o narzędziach typu grep, edytorze wsadowym sed oraz edytorach wywodzących się z systemu Unix vim i emacs. Wyrażenia regularne są intensywnie wykorzystywane w module mod_rewrite w Apache’u.
Również i dla systemu Windows stworzono kilka dedykowanych narzędzi, za pomocą których można przetwarzać tekst przy użyciu wyrażeń regularnych. W szczególności należy tu wymienić edytory tekstu, takie jak Edit Pad Pro, Textpad, Crimson Editor. Ten ostatni zresztą służyć będzie do obrazowania niektórych opisywanych na stronie przykładów (swoją drogą: polecam ten edytor gorąco, jest szybki, udostępnia wiele przydanych rzeczy, jak na przykład łączenie plików w projekty i do tego jest darmowy).
Wspomniałem wyżej o różnych językach programowaniach, wspierających wyrażenia regularne; otóż z dużym przybliżeniem można powiedzieć, że składni wyrażeń regularnych jest tyle, ile języków programowania. Rzecz jasna, różnice są w tych malutkich tyciuśkich szczegółach-szczególikach, na które przeciętny amator wyrażeń regularnych nigdy nie natrafi, ale, podkreślam, różnice są.
Z drugiej jednak strony, równie dobrym przybliżeniem będzie stwierdzenie, że, owszem, standard istnieje. Żeby było śmieszniej, to istnieją co najmniej dwa — albo cztery, zależy, jak patrzeć — standardy.
Pierwszy z nich to, opracowany przez IEEE, standard POSIX; definiuje on rzy rodzaje wyrażeń regularnych — proste (Simple Regular Expressions, SRE), podstawowe (Basic Regular Expressions, BRE) i rozszerzone (Extended Regular Expressions, ERE). Proste WR odszeszły w zapomnienie i używane są tylko pozostałe dwa.
Drugim standardem — choć nigdzie nie udokumentowanym — jest standard zaimplementowany w języku Perl. Historycznie, Perl wyrósł na kanwie POSIX-owych wyrażeń regularnych właśnie, a potem inkorporował kolejne ulepszenia, ustanawiając ostatecznie niepisany — niepisany, podkreślam — standard. Standard na tyle jednak znaczący, że jego obsługa została zaimplementowana w wielu różnych językach programowania — włączając Javę, JavaScript, Ruby’ego i Pythona. W międyczasie powstała również biblioteka, napisana w języku C, o nazwie PCRE, co rozwija się w Perl Compatible Regular Expressions; biblioteka ta została włączona do języka Perl w wersji 5.10 — swoista inkarnacja Uroborosa w świecie teleinformatyki. Wspominam o tym dlatego, że przez PCRE będę oznaczał właśnie smak WR występujących w Perlu.
W niniejszym dokumencie opiszę wszystkie — to znaczy BRE, ERE i PCRE-owe wyrażenia regularne. Cały powyższy wstęp miał na celu to jedynie, bo uczulić szanownego Czytelnika na to, że standardy WR różnią się między sobą; w niniejszym dokumencie chciałbym móc wyraźnie zaznaczać, które elementy WR przynależą do którego standardu. Długo myślałem nad tym, jak oznaczać wyrażenia z konkretnego standardu i wyszło mi, że nie ma opcji nad po prostu pisanie — słowami, co i jak.
Wyrażenia regularne mogą składać się ze znaków (na przykład: a
lub Litwo! Ojczyzno moja!
) i
metaznaków. Metaznaki wyglądają jak zwykłe znaki, jednak o ile zwykłe
znaki interpretowane są dosłownie (lub — z angielskiego — literalnie;
literał to sposób zapisywania wartości w kodzie programu, w naszym
przypadku programem jest mechanizm dopasowywania wzorców, a wartością —
dopasowywany tekst), co oznacza mniej więcej tyle, że litera P
dopasowana jest do P, to metaznaki nie są
dopasowywane w ten sposób; dodatkowo, mogą one — te metaznaki — wpływać
na zachowania sąsiadujących z nimi znaków.
Metaznakami są:
. + ? * ^ $ [ ( ) \ | {
czyli, mówiąc po ludzku: kropka, plus, znak zapytania, asterysk, karetka, dolar, nawias kwadratowy otwierający, nawiasy zwykłe, backslash, kreska pionowa (alternatywa), klamra otwierająca.
Specjalne znaczenie metaznaków najlepiej wytłumaczyć na przykładzie:
jeśli chciałbyś napisać wyrażenie regularne, które dopasowuje ciąg znaków
*.txt, to poprawnym wyrażeniem będzie \*\.txt
, a nie *.txt
. Dzieje się tak
dlatego, że zarówno kropka .
, jak i asterysk
\*
są właśnie metaznakami i nie są dopasowywane
„literalnie” (o czym dowiesz się już za moment).
W powyższym przykładzie widać jeszcze jedną cechę metaznaków: chcąc
dopasować metaznak należy poprzedzić go backslashem. Z angielska nazywa
się to to escape a character, co można — ale nie należy —
tłumaczyć jako „uciec znak”. Mi się to tłumaczenie podoba, nie wiem, jak
Tobie. Ale uwaga: operacji tej należy dokonywać tylko w przypadku
metaznaków, bo, przykładowo, w PCRE \s
nie
dopasowuje litery s, a znaki odstępu (np. spacje).
Tutaj ważna dygresja: w standardzie BRE znaki ( )
oraz { }
muszą być uciekane, jeśli mają być
traktowane jako metaznaki. Innymi słowy — standardowo znak (
traktowany jest literalnie, tj. jako nawias okrągły
otwierający, przez WR o smaku BRE.
Gwoli kompletności, należy wspomnieć w tym miejscu, że prawie wszystkie
metaznaki tracą swoje specjalne znaczenie, jeśli wziąć je w nawiasy
kwadratowe […]
, w celu wyróżnienia pewnego
zakresu znaków. Wówczas traktowanie są one literalnie. O tym, które znaki
nie tracą swego specjalnego znaczenia oraz o samych nawiasach
kwadratowych możesz przeczytać gdzie indziej.
To, jak w ogóle działają wyrażenia regularne i czym się je je, najlepiej zobaczyć na przykładzie. W tym celu weźmy sobie tekst:
Matko, w lesie są maliny Niechaj idą w las dziewczyny, Która więcej malin zbierze, Tę za żonę pan wybierze.„Balladyna”
Załóżmy, że chcemy dopasować wzorzec liny
.
Mechanizm dopasowywania wzorca bierze sobie ten wzorzec i zaczyna go
dopasowywać:
Matko, w lesie są maliny, liny
Wzorzec nie pasuje, więc porównywany jest następny znak wyrazu Matko, a potem następny, aż mechanizm natrafi na literę l:
Matko, w lesie są maliny, liny
Ponieważ litera ta odpowiada pierwszej literze wzorca, mechanizm sięgnie po jego kolejną literę, nie zostanie dopasowana:
Matko, w lesie są maliny, liny
Mechanizm działa słowo po słowie, aż do napotkania ostatniego wyrazu:
Matko, w lesie są maliny, liny
Następna litera wzorca również zgadza się z przeszukiwanym tekstem, mechanizm więc posuwa się dalej:
Matko, w lesie są maliny, liny
i dalej
Matko, w lesie są maliny, liny
i dalej, aż do ostatniego znaku wzorca:
Matko, w lesie są maliny, liny
Gdybyś, drogi Czytelniku, pod kątem wystąpienia tego samego wzorca chciał przeszukać linijkę trzecią
Która więcej malin zbierze,
rzecz wyglądałaby następująco. Krok pierwszy:
Która więcej malin zbierze, liny
Drugi:
Która więcej malin zbierze, liny
Trzeci
Która więcej malin zbierze, liny
I zonk:
Która więcej malin zbierze, liny
Sam rozumiesz, spacja to nie jest y.
Z tej lekcji należy wyciągnąć dwa wnioski:
Znak kropki .
pełni specjalną rolę w mechaznizmie
wyrażeń regularnych — odpowiada on bowiem (prawie) dowolnemu znakowi.
Zmieniając więc rozpatrywane wcześniej wyrażenie na lin.
, dopasowalibyśmy nie tylko cztery ostatnie litery słowa
maliny, ale również fragment słowa malin w trzecim wierszu strofki oraz
następującą po nim spację:
Która więcej malin zbierze, lin.
Jedyne, czego kropka nie dopasowuje, to znak końca wiersza.
Przykład Gdyby więc przeszukiwany przez nas tekst wyglądał następująco
Matko, w lesie są maliny, Niechaj idą w las dziewczyny, Która więcej malin zbierze, Tę za żonę pan wybierze.
to mechanizm wyszukiwania wzorca działałby następująco
Która więcej malin<CR><LF> lin.
Podsumowując: kropka dopasowuje dowolny znak poza znakiem końca wiersza.
Klasy znakowe służą do wyszukiwania znaków, które spełniają (bądź nie spełniają, ale o tym później) pewien warunek; można powiedzieć, że klasy znakowe są pewnym szczególnym – uszczegółowionym – rodzajem kropki.
Chcąc użyć klasy znakowej możemy podać zakres, odwołać się do jednej z predefiniowanych klas znakowych, odwołać się do jednej z predefiniowanych klas POSIX albo użyć jednej z właściwości Unicode (do zrobienia, tego jest naprawdę sporo, chwilowo jedynie link do oficjalnej strony Unicode o właściwościach właśnie).
Ponieważ ta część strony jest stosunkowo długa, poniżej jest mały spis reści, który umożliwia skakanie po poszczególnych sekcjach niniejszej strony:
Zakres definiujemy za pomocą nawiasów kwadratowych, w których umieszczamy
znaki, które chemy dopasować: [znaki]
. I tak,
wyrażenie regularne ko[cs]
dopasuje słowa
koc i kos (jak również skoczek i koszyczek), zaś [abcdefghijklmnopqrstuvwxyz]
dopasuje dowolną (małą!) literę
z alfabetu łacińskiego; w tym konkretnym przypadku, zamiast pracowicie
wypisywać wszystkie znaki można napisac [a-z]
,
osiągając ten sam efekt. Cyfrę szesnastkową dopasujemy, wpisując wzorzec
[0-9A-Fa-f]
.
W celu zaoszczędzenia czasu programistów (i zwykłych użytkowników również!) w PCRE wprowadzono następujące predefiniowane klasy znakowe, używanie których znakomicie ułatwia życie i zwiększa czytelność wyrażenia regularnego, co również nie jest bez znaczenia:
Symbol | Co znaczy |
---|---|
d
|
Cyfra |
D
|
Znak inny niż cyfra |
w
|
Znak wyrazu (litery, cyfry oraz podkreślenie) |
W
|
Znak inny niż znak wyrazu |
s
|
Odstęp |
S
|
Znak inny niż odstęp |
Przykład 1. Klasy znakowe a maliny
Przykładowo, weźmy tekst Która więcej malin zbierze
oraz wzorzec malin\w
. Mechanizm dopasuje pierszą
część wzorca malin, by następnie zakończyć wyszukiwanie
niepowodzeniem po napotkaniu znaku spacji. Z kolei wzorzec malin\s
zostanie dopasowany do wspomnianego tekstu.
Standard POSIX, zaimplementowany w BRE i ERE, definiuje własne klasy znakowe. Zapewniają one poprawną obsługę języków innych niż angielski (to miło, że twórcy standardu pomyśleli o innych narodach).
Poniżej umieściłem zestawienie wszystkich klas POSIX:
Symbol | Klasa | ASCII | Co dopasowuje |
---|---|---|---|
[:alnum:]
|
[a-zA-Z0-9]
|
znaki alfanumeryczne | |
[:alpha:]
|
[a-zA-Z]
|
litery | |
[:word:]
|
\w
|
[A-Za-z0-9_]
|
znaki alfanumeryczne oraz podkreślenie |
[:blank:]
|
[ \t]
|
spacja i znak tabulatora | |
[:cntrl:]
|
[\x00-\x1F\x7F]
|
znaki sterujące | |
[:digit:]
|
\d
|
[0-9]
|
cyfry |
[:graph:]
|
znaki drukowalne (widoczne) | ||
[:lower:]
|
[a-z]
|
małe litery | |
[:print:]
|
[\x20-\x7E]
|
znaki drukowalne (teraz z odstępem) | |
[:punct:]
|
znaki przestankowe | ||
[:space:]
|
\s
|
[ \t\r\n\v\f]
|
znaki odstępu |
[:upper:]
|
[A-Z]
|
wielkie litery | |
[:xdigit:]
|
[a-fA-F0-9]
|
cyfry szesnastkowe |
Zaletą klas jest, że dostosowują się one do ustawień lokalnych. I tak —
przynajmniej teoretycznie — [[:word:]]
dopasowuje
nie tylko litery od a do z, ale
również ą, ć i inne znaki
diakrytyczne.
Warto w tym miejscu nadmienić, że klasy POSIX zawsze są wzięte w nawiasy
kwadratowe, przez co, chcąc ich użyć jako wnętrze zakresu, musimy otoczyć
je dodatkowymi nawiasami np. [[:word:]]
.
Przykład 2. Klasy znakowe a maliny — ale inaczej
Wzorzec malin\w
może zostać zapisany jako
malin[[:word:]]
, zaś wzorzec malin\s
jako malin[[:space:]]
.
A dopasowanie cyfry szesnastkowej przy użyciu klas POSIX jest naprawdę bardzo proste :-)
A co jeśli nie chcemy dopasować litery a? Proste.
Wystarczy użyć znaku negacji, czyli daszka ^
w
połączeniu z podaniem zakresu.
I tak [^a]
dopasuje wszystkie znaki różne od
litery a, [^\d]
wszystko, co
nie jest cyfrą (czyli odpowiada dokładnie wyrażeniu \D
), zaś [^\da-fA-F]
wszystko, co nie
jest cyfrą szesnastkową.
Ważna rzecz: wyrażenie [^\d\w]
to nie to samo, co
[\D\W]
. Przykładowo, bowiem, pierwsze wyrażenie
nie dopasuje znaku podkreślenia _ (ze względu na
zanegowaną klasę \w
), ale [\D\W]
taki znak dopasuje. Dlaczego? Powiedzmy, że zostawiam
to jako ćwiczenia dla Czytelnika (podpowiedź).
W tym miejscu dwie uwagi — ze względu na to, że zarówno -
, ]
, jak i ^
mają specjalane znaczenie, jeśli umieścić je w nawiasach kwadratowych,
dlatego też chcąc dopasować daszek ^ nie należy
umieszczać go na początku; chcąc dopasować minus -
najbezpieczniej umieścić go jako ostatni znak w zakresie, a zamykający
nawias kwadratowy jako… pierwszy. I tak, []^-]
dopasuje ], albo \^, albo -. Ewentualnie, jeśli ktoś ma życzenie, może uciec każdy z tych
znaków. Slesz również musi być uciekany, zarówno w zakresie, jak i poza
nim. Chcąc dopasować znak \ używamy wzorca \\
.
Przykład 3. Klasy znakowe — to samo, ale inaczej
I tak, [-ac]
, [a\-c]
oraz
[ac-]
są równoważnymi wzorcami, ale takie
[a-c]
już nie :-)
Pozostałe metaznaki traktowane są literalnie, to jest dosłownie.
[.]
nie dopasowuje zatem dowolnego znaku, a znak
kropki.
Znaki powtórzeń fachowo nazywają się kwantyfikatorami wyrażeń regularnych, co wcale nie oznacza, że tej ostatniej nazwy należy się rzymać. Ten rozdział miał pierwotnie być zatytułowany „Kwantyfikatory”, ale już podczas pisania przyszło mi na myśl, że ta nazwa bardziej odstraszy niż zachęci czytelników.
Znaki powtórzeń umożliwiają nam dopasowywanie dopasowywanie wielokrotnych
wystąpień danego elementu wzorca. By dopasować liczbę czterocyfrową
napiszemy \d\d\d\d
, ale o wiele wygodniej — i
bardziej elegancko — jest skrócić to wyrażenie do \d{4}
, co tłumaczy się jako „dopasuj cyfrę dokładnie cztery
razy”.
Znaki powtórzeń swym zasięgiem obejmują poprzedzający je znak lub
ostatnią grupę znaków (grupa znaków to, hm, grupa znaków, która przez
mechanizm wyrażeń regularnych traktowana jest jako całość i oznacza się
ją za pomocą nawiasów (grupa znaków
) [Podkreślam, że to wyrażenie w standardzie ERE albowiem
ponieważ, w BRE należy uciekać nawiasy i klamry, pamiętasz?] ;
wzięcie części wyrażenia regularnego w nawias oznacza (poza innymi
konsekwencjami, o których dowiesz się później), że traktowana ona — ta
część — będzie jako całość. Co to oznacza jest wyjaśnione poniżej.
Ograniczając się do samego mięsa, można powiedzieć, że są trzy podstawowe
znaki powtórzeń: asterysk *
, plus +
i znak zapytania ?
(smak BRE jest
wyjątkowo ograniczony w tym kontekście i uznaje tylko symbol *
). Dopasowują one, odpowiednio: 0 albo więcej razy, 1 albo
więcej razy oraz 0 albo 1 razy.
Teraz są dość ważne rzeczy, więc tym razym przykłady stanowią osobną część rodziału.
Przykład 4. Kropka gwiazdka
Wyrażenie .*
dopasowuje dowolny ciąg znaków.
Kropka, jak wiesz, odpowiada (prawie) dowolnemu znakowi. Gwiazdka, jako
znak powtórzenia, „łączy” się z poprzedzającym ją znakiem; innymi słowy
wyrażenie o oznacza: „dopasuj (prawie) dowolne znaki dowolną liczbę
razy”. „[D]owolną liczbę razy” oznacza w tym przypadku tak zero, jak i
wiele razy. W szczególności do tego wzorca pasują linie puste, jak i te,
które zawierają dowolne znaki.
Przykład 5. Słowa, słowa, słowa
Wyrażenie [\w._]+
wyłapuje słowa (jedno- lub
więcej -literowe) , składające się cyfr, liter, podkreślenia i kropki.
Uważny czytelnik zwróci uwagę na jeden istotny detal — kropka umieszczona
w zakresie […]
swoje specjalne znaczenie i nie
rzeba jej uciekać.
Przykład 6. OŚ
Za pomocą ość?
dopasujemy słowa oś oraz ość (jak również kość, koś, skośny i tak
dalej). Znak zapytania ?
możemy interpretować
jako oznaczenie, że dany poprzedzający go znak (grupa znaków) jest
opcjonalna, to jest może występować, ale niebo nie spadnie nikomu na
głowę, jeśli w dopasowywanym wzorcu się nie pojawi.
Przykład 7. Wracamy do Balladyny
Grupa znaków wzięta w nawiasy traktowana jest jako całość i to do niej
właśnie odwołuje się następujący po niej znak powtórzenia; przykładowo,
wyrażenie maliny?
dopasuje słowa malin, jak również mailny (pamiętasz? znak
?
oznacza powtórzenie zero- lub jednokrotne). A
wyrażenie mali(ny)?
dopasuje słowa mali i maliny (ale nie malin!). Albowiem ponieważ znak ?
w
pierwszym przykładzie odwołuje się jedynie do litery y,
a w drugim do całej grupy znaków ny.
Znak powtórzenia postawiony za zakresem obejmuje swoim działaniem cały
zakres, a nie znak przez ten zakres dopasowany. Mówiąc po ludzku,
[a-z]+
dopasuje zarówno kot,
jak i ccc.
O powtórzeniach z góry zadaną liczbę razy już wspomniałem – to te
oznaczane klamrami {liczba powtórzeń}
. Można
używać także klamerek o nieco bardziej ogólnym charakterze, pamiętając,
że w BRE należy je uciekać:
{zakres dolny,}
Dopasowuje poprzedzający znak lub grupę znaków zakres dolny lub więcej razy.
{zakres dolny,zakres górny}
Dopasowuje poprzedzający znak lub grupę znaków co najmniej zakres dolny, ale nie więcej niż zakres górny razy.
Przykład 8. Liczby parzystocyfrowe (jakie?!)
Jeśli rozważmy sobie wyrażenie \d{2}(\d{2})+
,
to po — krótkim — przyjrzeniu mu się, dostrzeżemy, że dzięki niemu
możemy dopasować dowolną parzystocyfrową liczbę (co najmniej
dwucyfrową, rzecz jasna). A dzięki wyrażeniu \d{2}(\d{2})
? możemy sprawdzić, czy wprowadzona przez
użytkownika liczba może być potraktowana jako numer roku, który
zazwyczaj ma dwie lub cztery cyfry [To znowu
tylko nieudolna próba walidacji. No bo co ze sprawdzeniem, że rok ma
sens, to jest nie jest z przyszłości, albo z bardzo zamierzchłej
przeszłości?] .
Mamy więc następujące grupy kwantyfikatorów:
*
— 0 lub więcej razy
+
— 1 lub więcej razy
?
— 0 lub 1 jeden raz
{n}
— dokładnie n razy
{n,}
— co najmniej n razy
{n,m}
— co najmniej n i nie więcej niż m razy
Jeśli teraz, drogi czytelniku, czujesz się mądry i pragniesz sprawdzić
swoje siły — napisz, proszę, wyrażenie, które dopasowywać będzie
znaczniki HTML. Podpowiem, że jeśli sądzisz, że jest to <.+>
, to jesteś w błędzie.
Dlaczego? Dlatego, że mechanizm dopasowywania wzorców jest zachłanny!
Mechanizm dopasowywania wzorców jest zachłanny.
W terminach informatycznych oznacza to, że mechanizm dopasowywania wzorców dopasowuje tak dużo, jak mu się uda. Nie mniej.
Najprostszy przykład to wzorzec dopasowujący znaczniki HTML, który zaleciłem napisać czytelnikowi pod koniec rozdziału o powtórzeniach.
Dla ustalenia uwagi rozważmy następujący fragment kodu:
<p>To jest kod <abbr>HTML</abbr> taki sobie↵ <em>tam</em>.</p>
Narzucające się wyrażenie <.+>
(dopasuj
znak <, dowolną — acz niepustą — treść za nim,
wszystko zakończone znakiem >) działa niezgodnie z
zamierzeniami. A jak ono działa?
Najpierw dopasowywany jest znak <, który otwiera
znacznik <p>. Następnie, kropka z plusem
dopasowuje literkę p, a chwilę później znak >. Myślisz, że na tym mechanizm kończy swoje działanie?
Otóż nie! Bo i czemuż miałby? > przecież pasuje
przecież do wzorca .+
! Mechanizm jest zachłanny
— nie rezygnuje z dopasowania, jeśli może dopasować. I tak wędruje
kropka z plusikiem przez cały nasz kod, aż natrafiając na koniec
wiersza. W tym momencie dopasowany tekst to <p>To
jest kod <abbr>HTML</abbr> taki sobie
<em>tam</em>.</p>, a mechanizm orientuje się, że
troszkę się zagalopował, bowiem został mu jeszcze jeden znak wyrażenia
do dopasowania, a mianowicie >
. Mechanizm w
tym momencie skraca dopasowany tekst do <p>To jest
kod <abbr>HTML</abbr> taki sobie
<em>tam</em>.</p, jednocześnie zaczynając, zamiast
kropki, dopasowywać znak większości. Et voila, ostatni znak
przeszukiwanego przez nas ekstu pasuje; mamy więc dopasowany ciąg od
jego początku aż do samego końca.
Jeden obraz mówi więcej niż tysiąc słów, przyjrzyjmy się więc jak takie dopasowywanie wygląda w Crimson Editorze:
Wprowadzamy wyrażenie i po kliknięciu na Find next w wyniku otrzymujemy zaznaczoną całą frazę:
Nie jest to jednak to, o co nam chodziło.
Wyrażenie .+
przeszukające „wnętrze” znaczników
HTML dopasowuje zbyt dużo; gdyby zakazać mu (?) reagować na znak
<, który otwiera nowy znacznik, wówczas wyrażenie
działałoby zgodnie z naszymi oczekiwaniami, prawda?
Ponieważ znamy już znak negacji, zadanie jest proste: wystarczy napisać
<[^<]+>
, by zawęzić zakres znaków, na
które wyrażenie reaguje. Teraz wszystko działa, jak trzeba:
Otrzymujemy:
Działa!
Niepoprawne Niezgodne z oczekiwaniami działanie wyrażenia
<.+>
wynika z zachłanności znaków
powtórzeń. Szczęśliwie, mechanizm dopasowywania wzorców udostępnia nam
możliwość przeinaczenia go — tego mechanizmu — w prawdziwego lenia,
który, po odwaleniu swojej roboty, idzie na fajrant.
Efekt ten uzyskuje się poprzez postawienie znaku zapytania ?
po znaku powtarzania. Można go postawić po wszystkich
kwantyfikatorach wymienionych w rozdziale o powtórzeniach, to jest po
asterisku *
, plusie +
,
znaku zapytania ?
, klamerkach {zakres dolny,}
i {zakres dolny, zakres
górny}
(dostawianie ?
po {liczba powtórzeń}
nie ma, przyznasz, sensu).
I w ten oto sposób mamy:
n*?
dopasowuje n zero lub więcej
razy (czytaj: dowolną liczbę razy), ale tyle jedynie, ile to
konieczne;
n+?
dopasowuje n raz lub więcej,
ale czyni to najmniejszą możliwą liczbę razy;
n??
dopasowuje n zero lub jeden
raz, z preferencją ku zeru razy;
n{a,b}?
dopasowuje n przynajmniej
a razy, co najwyżej b razy, ale czyni to
najmniejszą możliwę liczbę razy;
n{a,}?
dopasowuje n co najmniej
a razy, ale czyni to najmniejszą liczbę razy.
Leniwe znaki powtórzeń nazywamy również kwantyfikatorami minimalistycznymi.
Przykład 9. Kwantyfikatory minimalistyczne a znaczniki HTML
Wyrażenie <.+?>
dopasuje znaczniki HTML.
Dlaczego?
Przyjrzyjmy się dokładnie sposobowi, w jaki powyższe wyrażenie działa.
Dla ustalenia uwagi przyjmijmy, że znacznikiem jest <EM>. Oczywiście <
dopasowuje
się do znaku mniejszości <, otwierającego
znacznik. Następnie jest kropka, po której użyliśmy leniwego plusa.
Plus dopasowuje co najmniej jedno wystąpienie poprzedzającego go znaku,
w tym przypadku może to być znak dowolny. W naszym przypadku jest to
E. Świetnie, plus może skończyć swoją robotę, gdyż
już dopasował, co miał dopasować (przynajmniej jeden dowolny znak).
Wyrażenie regularne sięga po znak następujący po E,
czyli M, chcąc go dopasować do >
. Dopasowanie nie udaje się, więc do pracy musi znowu
przystąpić leniwy plus, poprzedzony kropką; mechanizm dopasowywania
znaków w tym momencie cofa się w przeglądaniu wzorca. Kropka dopasowuje
M, plus ponownie kończy swoją robotę (uff), mechanizm
dopasowywać będzie teraz >
. Tak się składa,
że dopasowanie teraz się uda, gdyż > jest po
świeżo znalezionym M. Mechanizm kończy pracę.
Przykład 10. Lorem ipsum dolor
Rozważmy tekst Lorem ipsum dolor sit amet oraz
wyrażenie regularne m.*m
.
W tej formie działa ono następująco: dopasowuje m,
następnie powtarza dopasowanie do momentu, aż natrafi na znak końca
wiersza, ponownie orientując się, że został mu jeszcze jeden element
wzorca do dopasowania — m
. Dopasowany tekst w
tym momencie to m ipsum dolor sit amet. Mechanizm
cofa się w dopasowaniu, zmniejszając tekst najpierw do m
ipsum dolor sit ame, następnie, po nieudanej próbie dopasowania
m
, do m ipsum dolor sit am,
na czym kończy pracę, gdyż ostatnia litera dopasowanego tekstu pasuje
do dopasowywanego fragmentu wzorca. Finito!
W przypadku wyrażenia m.*?m
sprawa wygląda
inaczej. Mechanizm, po dopasowaniu litery m zaczyna
dopasowywać fragment m
wzorca, bo przecież
*
oznacza zero lub więcej znaków. Dodając
?
rozleniwiliśmy asterisk, przez co ten,
dopasowawszy zero dowolnych znaków, kończy swoją pracę. Mechanizmowi,
oczywiście, nie uda się dopasować wzorca m
;
gwiazdka znowu będzie musiała zacząć pracę, dopasowując jeden znak.
Dopasowany wzorzec w tym momencie to m , czyli
ostatnia litera słowa Lorem i następująca po nim spacja. Mechanizm
znowu próbuje dopasować m
, znowu mu się nie
udaje i znowu gwiazdka z kropką dopasowują kolejny znak. Dzieje się tak
aż do dopasowania m ipsu; mechanizm chce teraz
dopasować m
do kolejne znaku w tekście i tym
razem mu się to uda, osiagając ostateczną formę dopasowanego tekstu
m ipsum.
Tuszę, że powyższe przykłady pozwalają lepiej zrozumieć mechanizm dopasowywania wzorców. Jeśli nie rozumiesz czegoś do końca, to przeczytaj tekst ponownie, gdyż rzeczy omawiane w tym rozdziale są naprawdę ważne.
Standardowo, mechanizm dopasowywania wzorców dopasuje wzorzec do tekstu
niezależnie od położenia tego ostatniego; przykładowo, w poniższym
tekście wyrażenie A dokąd
dopasuje wszystkie
A dokąd:
A dokąd? A dokąd? A dokąd? Na wprost!
A powiedzmy, że chcemy dopasować tylko to A dokąd, które jest na początku? No to co wtedy?
Jak to co — użyć kotwicy. Kotwice to metasymbole, które określają położenie poprzedzającego lub następującego po nich znaku (grupy znaków).
Mamy następujące rodzaje kotwic:
Symbol | Co znaczy |
---|---|
^
|
Początek linii |
$
|
Koniec linii |
\b (PCRE)
|
Znak wyrazu pomiędzy znakiem wyrazu a pomiędzy znakiem niewyrazu, czyli pomiędzy \w\W lub \W\w |
\B (PCRE)
|
Wszystko to, czego nie dopasowuje \b |
Przykład 11. Puste linie
Wyrażenie ^$
dopasuje wszystkie puste linie w
dokumencie, a wyrażenie ^#
wszystkie linie,
które zaczynają się od hasha # (komentarze w
programie?).
Przykład 12. A dokąd
Chcąc znaleźć pierwsze A dokąd z wyżej
umieszczonego fragmentu Lokomotywy, wystarczy zmodyfikować wyrażenie
A dokąd
, umieszczając na początku daszek;
^A dokąd
dopasuje A dokąd,
które rozpoczyna wiersz.
Przykład 13. boktb
Wyrażenie \bkot\b
dopasuje słowo kot, ale nie kotłownia, ani nie stukot. I, analogicznie wyrażenie \Bkot\B
dopasuje słowo skotłowany i
żadnego z wymienionych wcześniej w tym przykładzie. Czytelnik sam
zorientuje się, co jaki napis dopasuje \Bkot\b
, a jaki \bkot\B
.
Wyobraź sobie, że chcesz znaleźć osoby, których numer PESEL zaczyna się tą samą cyfrą, którą się kończy — za pomocą wyrażeń regularnych, mądralo, a nie w jakimś języku programowania!
Można to zrobić, wypisując wszystkie możliwe kombinacje wyrażeń
postaci cyfra\d{9}cyfra
. Ale po co? Prościej
i łatwiej użyć grupowania i przechwytywania, prawda?
O grupowaniu już pisałem przy okazji powtórzeń, warto jednak wspomnieć o nim jeszcze raz, w kontekście niniejszego rozdziału.
Grupowanie umożliwia wyodrębnienie pewnej części wyrażenia regularne,
która przez mechanizm dopasowywania wzorców jest traktowana jako
swoista całość; jak w przykładzie czwartym z rozdziału o
powtórzeniach, następujący po zgrupowanym fragmencie wyrażenia znak
plusa odnosił się do tej grupy właśnie, a nie bezpośrednio
poprzedzającego go znaku. Podobnie Sty(czeń?)
dopasuje napis Sty, jak i Styczeń
(ale nie Stycz).
To świetnie, prawda? Bardzo użyteczny przykład, który — póki co — w żaden sposób nie ułatwia wykonania opisanego we wstępie niniejszego rozdziału zadania. Niemniej, właśnie mechanizm grupowania (wraz z przechwytywaniem) pomoże je — te zadanie — rozwiązać.
Otóż wszystkie dopasowane fragmenty napisów, dopasowane do
zgrupowanych części wyrażenia, zostają przeniesione do specjalnych
zmiennych mechanizmu dopasowywania wzorców, do wartości których
możesz się odwoływać (przechwycone napisy określa się również mianem
odwołań wstecznych). Zmienne te oznaczane są za pomocą backslasha i
cyfry cyfra; przykład z początku rozdziału można „rozwiązać” używając
wyrażenia (\d)\d{9}1
, co tłumaczy się jako:
„znajdź napisy zawierające jedenastocyfrowe liczby, w których
pierwsza cyfra jest taka sama, jak ostatnia”. Zwróć uwagę na
odwołanie \1
– zawiera ono wartość dopasowaną
przez zgrupowanie (\d)
; dzięki takiemu
wzorcowi pierwsza cyfra faktycznie będzie taka sama, jak ostatnia!
Mechanizm umożliwia przechwycenie maksymalnie dziewięciu napisów.
Już sam ten fakt może powodować pewne niedogodności; w praktyce bowiem dość często natknąć się można na wyrażenia regularne, które zawierają o wiele więcej niż dziewięć czy nawet dwadzieścia grup. Odwołanie się do jedenastego przechwyconego napisu jest — przy użyciu „tradycjnych” metod — niemożliwe.
Szczęśliwie mądrzy ludzie (tj. autorzy Perla, bo sekwencja znaków, o
której zaraz napiszę działa w PCRE) udostępnili możliwość wyłączenia
przechwytywania; za pomocą specjalnej sekwencji znaków możemy je – to
przechwytywanie – wyłączyć, oszczędzając numerację zmiennych
\cyfra
na naprawdę ważne fragmenty tekstu.
W tym celu zamiast zwyczajnych nawiasów (fragment
wzorca)
należy użyć sekwencji (?:fragment
wzorca)
.
I tak wyrażenie (?:Numer)?(\d)\1
znajdzie w
przeszukiwanym fragmencie ekstu wszystkie liczby dwucyfrowe, złożone
z tych samych cyfr, którą mogą być — ale nie muszą - poprzedzone
napisem Numer (czyli, przykładowo, Numer11 lub 00, ale nie Numero22, czy też 93). Nie wykorzystując
„rozszerzonego zestawu sekwencji” napisalibyśmy (Numer)?(\d)\2
, co w tym przykładzie wielce ograniczające
nie jest, niemniej, jak napisałem wcześniej, może się złożyć, że
poziom komplikacji wyrażenia regularnego będzie wymagał od nas
oszczędnego korzystania z dostępnych zmiennych przechwycających.
Zadanie dla Czytelnika.
Co będzie w \1
, co w \2
, a co w \3
po przepuszczeniu
przez wzorzec ((\w+) (\w+))
tekstu Czerwony Kapturek?
Czy jeśli podpowiem, że w zagnieżdżonych grupach, kolejne grupy liczone są względem lewego nawiasu, to będzie ci łatwiej?
Bo to proste jest, istotnie! W \1
znajdzie
się tekst Czerwony Kapturek. I dalej nie ma już nic
nadzwyczajnego – do \2
trafi Czerwony, a do \3
Kapturek.
I kolejne zadanie: czym różnią się wyrażenia (\d)+
od (\d+)
?
W przypadku pierwszym, dopasowana zostanie cyfra — jeden lub więcej
razy — a następnie zachowana w zmiennej \1
; w
przypadku drugim dopasowana zostanie jedno- lub więcejcyfrowa liczba,
która zostanie zapamiętana w zmiennej \1
.
Jak wiadomo, mechanizm dopasowywania wzorców umożliwia przeszukiwanie tekstu pod kątem kilku — określonych przez użytkownika — wariantów; ze względu jednak na fakt, że warianty te różnią się między sobą co najwyżej jednym znakiem, rozwiązanie to znajduje zastosowanie jedynie w niektórych przypadkach.
W ogólnym przypadku lepiej użyć alternatywy |
, wprowadzonej w smaku ERE. Przykładowo, kocha|lubi|szanuje
dopasuje jeden z trzech wyrazów kocha,
lubi albo szanuje. Kolejne przykłady:
Przykład 14. Ząb zupa zębowa, dąb zupa dębowa
z|d|r|ąb
dopasuje ciągi znaków z, d, r albo ąb.
Przykład 15. Teraz poprawnie
(z|d|r)ąb
dopasuje słowa ząb, dąb albo rąb, a
pierwsza literę słowa (z, d albo r) zapamięta w zmiennej \1
.
Przykład 16. Poprawnie i bez przechwytywania
(?:z|d|r)ąb
robi to, co wyrażenie w
przykładzie wyżej, ale nie zapamiętuje litery w zmiennej \1
.
Mechanizm dopasowuje wyrazy alternatywy po kolei, to znaczy wpierw
dopasowywany jest pierwszy wyraz alternatywy, następnie drugi i
kolejne. Oznacza to, że niezależnie od tego, do jakiego napisu
wzorzec czar|czarodziej
będzie przyrównywany,
mechanizm dopasuje słowo czar (nawet jeśli
mechanizm działa na napisie czarodziej!).
Rozwiązaniem w tej sytuacji jest zmiana kolejności wyrazów
alternatywie na czarodziej|czar
.
Również, jeśli rozważamy tekst Paweł i Gaweł w jednym
stali domu oraz wyrażenie (Gaweł|Paweł)
,
to wyrażenie dopasuje słowo… no, które? No, Paweł
oczywiście, gdyż stoi ono jako pierwsze w tekście i nie jest w tym
przypadku istotne, że w alternatywie to Gaweł stoi przed Pawłem.
Bardzo ważne jest posiadanie świadomości, że pewne elementy wyrażeń
regularnych mają większą wagę od innych elementów; wyrażenie
^mały|średni|duży$
działa nie tak, jak
oczekujesz, ale można to poprawić, nadając mu postać ^(mały|średni|duży)$
.
Warto więc zapoznać się z kolejnością priorytetów, według której mechanizm parsuje wyrażenie:
( )
(?:
)
;
?
+
*
{zakres dolny, zakres
górny}
??
+?
*?
{zakres dolny, zakres
górny}?
;
alamakota
^
$
;
|
.
Teraz już Czytelnik wie, że a|b*
oznacza
dopasuje literę a bądź też dowolną (czyli zero
również!) liczbę wystąpień litery b, a (a|b)*
dopasuje tekst składający się z litery a bądź litery b powtórzonych dowolną
liczbę razy (również zero razy!).
Strona jest bardzo stara, bo jej pierwsze wersje utworzyłem w roku 2005. Swego czasu dokument był dość popularny, przez pewien czas linkowała tu nawet polska wikipedia. A potem moją stronę zjadł wirus i odnośnik z wikipedii zniknął.
Tak czy inaczej, dokument swoje lata już ma. Przez dłuższy czas nie aktualizowałem jego treści, wychodząc z założenia, że to, co napisałem jest dobre i koszerne. Nie wchodząc w szczegóły i zbyt długie rozważania na temat zjawiska „samozadowolenia”, stwierdzić należy, że dokument nie był kompletny ani — co wstyd powiedzieć — poprawny. Oczywiście, w większości rzeczy tu napisane są prawdziwe, a podane przykłady powinny działać zgodnie z oczekiwaniami autora niniejszej strony, niemniej błędy były. I pewno nadal są.
Żeby więc uporządkować cały proces edycji tej strony, postanowiłem wprowadzić wersjonowanie. Dzięki temu P.T. Czytelnik będzie wiedział, czy i co się zmieniło.
Niniejszy dokument jest opublikowany w ramach licencji CC0.