Łukasz Borchmann

Alternatywne dopasowania i nawiasy

Załóżmy, że w znanym nam pliku slowoformy.txt, chcemy odnaleźć wszystkie słowa zawierające przedrostki pochodzenia greckiego, takie jak aero-, andro-, antropo- czy archeo-. Realizacja tego zadania (przy użyciu jednego wyrażenia) wymaga wprowadzenia dotychczas niewpominanego symbolu |, za którym kryje się alternatywa.

^aero|^andro|^antropo|^archeo

Wizualizacja wyrażenia ^aero|^andro|^antropo|^archeo.

Słowa rozpoczęte albo na aero-, albo na andro-, albo na antropo-, albo na archeo- odnajdziemy poleceniem grep -E '^aero|^andro|^antropo|^archeo' slowoformy.txt. Gdybyśmy nie zastosowali znaku ^, oznaczającego – jak pamiętamy – początek dopasowania (zakotwiczenia), to zwrócone byłyby również takie wyrazy jak synantropom czy salamandrowatych.

^(aer|andr|antrop|arche)o

Wizualizacja wyrażenia ^(aer|andr|antrop|arche)o.

Mimo to, wspomniana powtarzalność doprasza się uproszczenia u każdej osoby, której nauczyciel matematyki krzywił się na widok wyniku 5x + 5y i nakazywał wyciągnięcie wspólnego czynnika przed nawias, tak by działanie kończyło się równoważnym 5(x + y). Tutaj też możemy „wyciągnąć wspólny czynnik przed nawias” i zapisać grep -E '^(aero|andro|antropo|archeo)' slowoformy.txt, a ktoś nadgorliwy mógłby również (ze stratą dla czytelności) ująć tę samą regułę jako grep -E '^(aer|andr|antrop|arche)o' slowoformy.txt.

Jak znaleźć kropkę? O znakach ucieczki (modyfikacji)

Chcąc szukać symboli specjalnych, dla których w formalizmie wyrażeń regularnych zarezerwowano pewne funkcje (takich jak kropka oznaczająca dowolny znak), należy je poprzedzić symbolem \ (mówimy, że jest on znakiem ucieczki lub modyfikacji) i tak:

  • by wyszukać linie zakończone kropką zastosowalibyśmy grep -E '\.$' plik.txt, a nie grep -E '.$' plik.txt (to drugie wyrażenie odnalazłoby linie zakończone dowolnym znakiem);
  • by wyszukać linie rozpoczęte dwoma znakami ^, użylibyśmy grep -E '^\^\^' plik.txt;
  • żeby odnaleźć linie zawierające backslash użylibyśmy grep -E '\\' plik.txt (backslash poprzedzony backslashem).

Podobnie postąpilibyśmy w przypadku znaków takich jak $, *, +, ?, (, ), [, { czy | (o niektórych z nich nie było jeszcze mowy).

Skrócone oznaczenia klas (zbiorów) znaków

W poprzedniej części przedstawione zostały klasy znaków, takie jak [A-Za-z] czy [0-9]. Poza nimi istnieją skrócone oznaczenia pewnych popularnych klas, które przedstawiono poniżej.

  • \s odpowiada na ogół klasie [ \t\r\n\f], tj. zawiera różnego typu białe znaki, takie jak spacja, tabulacja czy też znak przejścia do nowej linii.
  • \w obejmuje znaki umownie zaliczane do mogących tworzyć słowa (stąd w, od word character) – zawiera w sobie m.in. zbiór, który moglibyśmy zdefiniować jako [A-Za-z0-9_], ale poza nim zawiera także opatrzone diakrytami znaki języków narodowych czy innego typu grafemy niewchodzące w skład łacinki (takie jak polskie [ąćęłńóśżźĄĆĘŁŃÓŚŻŹ] czy niemieckie [äöüÄÖÜß]). Innymi słowy obejmuje on (w przypadku większości implementacji) dowolne wielkie lub małe litery, cyfry oraz znak _.
  • \d jest synonimem [0-9] i jest równoznaczny [0123456789], czyli należy go rozumieć jako zbiór wszystkich cyfr arabskich.

Skrócone oznaczenia stosować można w nawiasach kwadratowych, albo bez nich. W tym pierwszym przypadku używać ich można do wyłączenia pewnych znaków ze zbioru, o czym była mowa poprzednio. Przykładowo, [^\d] oznacza dowolny znak, który nie jest cyfrą i jest równoznaczne znanemu już [^0-9], a [^\s] dopasuje się do dowolnego znaku, który nie jest spacją, tabulatorem, znakiem nowej linii lub innym białym.

I te wyłączenia mają jednak zapisy skrócone – przyjęto konwencję, zgodnie z którą wyłączenie danej skróconej klasy oznaczyć można taką samą literą, ale wielką w miejsce małej tak że \S jest równoznaczne [^\s], \D odpowiada [^\d], zaś \W to to samo co [^\w].

Granica słowa

Znak \b wygląda jak skrócone oznaczenie klasy przedstawione wyżej, ale jego zasada działania bliższa jest ^ czy $, które stosowaliśmy do zakotwiczenia dopasowania (rozpoczęcia go od początku i/lub końca).

Załóżmy, że posiadamy plik o poniższej zawartości i chcemy wykorzystać komendę grep do zwrócenia jedynie tych linii, które zawierają słowo mózg w formie podstawowej (podświetlone):

Zauważmy, że:

[Mm]ózg[\s\.]

Wizualizacja wyrażenia [Mm]ózg[\s\.].

  • nie możemy zastosować polecenia grep -E 'mózg' plik.txt, ponieważ zwrócona zostanie m.in. linia 1. (ze względu na słowo mózgowej), której nie chcemy;
  • w przypadku użycia grep -E '\smózg\s' plik.txt (słowo mózg między białymi znakami) nie zostanie zwrócona linia 3. (ponieważ słowo Mózg znajduje się na początku linii, a poza tym jest pisane wielką literą), ani 6. (ponieważ po słowie mózg znajduje się kropka);
  • zadania nie spełni grep -E '[Mm]ózg[\s\.]' plik.txt, ani grep -Ei 'mózg[\s\.]' plik.txt, ponieważ dopasowana zostanie ostatnia linia ze słowem Bezmózg.

Z pomocą przychodzi wspomniane \b, oznaczające granicę słowa, którą może być początek linii, koniec linii, biały znak, znak interpunkcyjny lub inny nieobecny w klasie (zbiorze) \w. Poprawna komenda to grep -E '\bmózg\b' plik.txt.

Garść przykładów

Zaprezentowana część formalizmu wyrażeń regularnych ma bardzo duże możliwości. Poniżej kilka bardziej złożonych przykładów jej zastosowania:

  • ^(19|20)\d{2}$ – rok z przedziału 1900-2099;
  • ^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$ – adres e-mail (wyrażenie skuteczne w miażdżącej większości przypadków);
  • \b(50|51|53|57|60|66|69|72|73|78|79|88)\d[\s-](\d{3}[\s-]\d{3}|\d{2}[\s-]\d{2}[\s-]\d{2})\b – polskie numery telefonów komórkowych wewnątrz tekstu, zapisane na różne sposoby (wyrażenie wykorzystane w moim artykule);
  • ^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$ – adres strony internetowej.

Przy ich czytaniu (lub szukaniu błędów w napisanych przez nas wzorcach) można wspomóc się narzędziem do wizualizacji, np. jex.im/regulex/, debuggex.com czy regexper.com, z którego to pochodzą schematy umieszczone w niniejszym wpisie.

Uwagi końcowe

Zachowanie wyrażeń regularnych może się w niewielkim stopniu różnić w zależności od programu czy języka programowania, w którym je wykorzystujemy – powyższe uwagi mogą okazać się więc w jakimś użyciu częściowo nieadekwatne, mimo, że ich zasadnicza część będzie bezwyjątkowo prawdziwa.

Wartościowymi źródłami wiedzy o zaawansowanych wyrażeniach regularnych jest strona internetowa regular-expressions.info oraz książka Mastering Regular Expressions, którą pożyczyć można w niezastąpionym serwisie stworzonym przez rodaków Perelmana.