1 Wstęp

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.

1.1 Programy i języki programowania ze wsparciem wyrażeń regularnych

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).

do góry ↑

2 Smaki i notacja

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.

do góry ↑

3 Metaznaki

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.

3.1 Uciekanie metaznaków

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.

do góry ↑

4 Jak to działa

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”
— Juliusz „Wielki Poeta” Słowacki

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:

do góry ↑

5 Kropka

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.

do góry ↑

6 Klasy znakowe

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:

6.1 Zakresy

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].

6.2 Predefiniowane klasy znakowe

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.

6.3 Predefiniowane klasy POSIX

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 :-)

6.4 Negacja

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ą.

6.5 Negowanie predefiniowanych klas znakowych

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ź).

6.6 Metaznaki a zakresy

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.

do góry ↑

7 Powtórzenia

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.

7.1 Przykłady

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 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.

7.2 Powtarzanie zakresu

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.

7.3 Klamerki

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:

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!

do góry ↑

8 Zachłanność

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:

Wyszukiwanie wzorca w Crimson Editorze

Wprowadzamy wyrażenie i po kliknięciu na Find next w wyniku otrzymujemy zaznaczoną całą frazę:

Wyszukana cała fraza

Nie jest to jednak to, o co nam chodziło.

8.1 Rozwiązanie pierwsze – negacja

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:

Wyszukanie właściwego wzorca w Crimson Editorze

Otrzymujemy:

Wyszukana właściwa fraza

Działa!

8.2 Rozwiązanie drugie – leniwe znaki powtórzeń

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:

Leniwe znaki powtórzeń nazywamy również kwantyfikatorami minimalistycznymi.

8.3 Przykłady

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.

do góry ↑

9 Pozycje

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!
„Lokomotywa”
— Julian Tuwim

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.

do góry ↑

10 Odwołania

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?

10.1 Czym to się je?

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).

10.2 Przechwytywanie

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!

10.3 A co jeśli nie chcę przechwytywać?

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.

10.4 Grupa w grupie w grupie

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.

do góry ↑

11 Lub

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.

11.1 Ważne szczegóły

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.

do góry ↑

12 Priorytety

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:

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!).

do góry ↑

13 O dokumencie

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.

13.1 Historia wersji dokumentu

13.2 Licencja

Niniejszy dokument jest opublikowany w ramach licencji CC0.