Podstawy używania wyrażeń regularnych


Wyrażenie regularne to wzorzec, który może pasować (lub nie) do określonego fragmentu tekstu. Najprostszym takim wzorcem jest podanie, znak po znaku, jak ma wyglądać jakiś fragment - to jest najczęściej robione przy wyszukiwaniu, tzn. wycinania z tekstu kawałków pasujących do wzorca. Jednak często potrzebujemy też wyłapać nie konkretny tekst, a np. jakiś wycinek linii, gdzie nie wiemy, jaka będzie jego zawartość, a tylko, dla przykładu, po czym i przed czym on występuje.

Wyrażenia regularne pojawiają się w różnych odmianach, w zależności od programu je interpretującego. Różnice polegają na ogół na rozróżnianiu znaków specjalnych od zwykłych (jeden program traktuje znak "[" jako znak specjalny, a inny może go traktować jak zwykły). Niektóre programy dodają do standardowych konstrukcji wyrażeń, zdefiniowanych w standardzie POSIX, własne rozszerzenia, które niekoniecznie muszą być kompatybilne z innymi programami. W tym tekście postaram się skupić przede wszystkim na tych "bazowych" regexpach (ang. regular expression - wyr. regularne), a w wypadkach różnic w implementacji będę pokazywał odpowiednio więcej przykładów, tak aby było widać, jaka jest ogólna zasada.

W kwestii notacji: w przykładach oznaczenie na końcu <xxxxx> oznacza, że dana relacja zachodzi, jeśli użyjemy programu xxxxx. Symbol " ~ " oznacza, że "wyrażenie pasuje do wzorca". Sekwencja "!~" oznacza "nie pasuje do wzorca".

Większość znaków ASCII zachowuje swoje zwykłe znaczenia (jeśli nie są użyte wewnątrz innych konstrukcji): litery, cyfry, przecinek, dwukropek, srednik, wykrzyknik... Wystąpienie takiego znaku w wyrażeniu oznacza po prostu "tu musi się pojawić taka literka". Przykład:

abcd ~ abcd

Słowo "abcd" pasuje do wzorca "abcd" - to chyba jest oczywiste :) Wiele programów jako prawdziwą postrzega też relację:

abcd ~ xxabcdyy

(bo ciąg liter "abcd" występuje w słowie "xxabcdyy")

Jeden z dwóch podstawowych znaków specjalnych to "." (kropka). Kropka oznacza dowolny znak. Przykłady:

abcd ~ a.c.
axcy ~ a.c.
axxxb ~ a...b

Wyrażenie "a.c." oznacza: najpierw "a", potem dowolny znak, potem "c", potem znowu dowolny znak - czyli zarówno "abcd" jak i "axcy" do niego pasują.

Drugi podstawowy znak to "*" (gwiazdka). Oznacza on: dowolnej długości ciąg znaczków takich jak poprzedni element wyrażenia. Przykłady:

aaaaa ~ a*
aaaaaaaaaaaaaaaaaa ~ a*
aaabbbbbbb ~ a*b*

"aaaaa" to ciąg liter "a" - pewnej długości, ale jakiej to nas nie interesuje, bo gwiazdka reprezentuje wszystkie takie ciągi. W związku z tym zarówno "aaaaa" jak i "aaaaaaaaaaaaaaaa" pasują do "a*". Co ciekawe, do tego wyrażenia pasuje też ciąg pusty: dowolna ilość a to też zero wystąpień a. Czyli "" ~ "a*". Natomiast "a*b*" oznacza: dowolnej długości ciąg literek a, a po nich ciąg literek b.

W ogólnym przypadku, jeśli jakiś znak reprezentuje znak specjalny (".","*"), to jeśli chcemy się pozbyć jego specjalnego znaczenia, wystarczy poprzedzić go znakiem "\" - jeśli "." oznacza dowolny znak, to "\." oznacza dokładnie znak ".". I na odwrót: czasem jakiś znak jest uważany za zwykły, dopóki nie poprzedzimy go "\" - np. jeśli litera "x" jest zwykłym znakiem, to "\x" może być znakiem specjalnym (ale nie musi). W szczególności "\\" oznacza po prostu znak "\" :)

Do podstawowych znaków "poprzedzanych" należą: \t (tabulator), \n (znak nowego wiersza). Odmianą gwiazdki jest "+" (plus) - generalnie znaczy on to samo so gwiazdka, czyli ciąg znaków dowolnej długości, poza ciągiem o zerowej długości: czyli o ile "" ~ "a*" jest prawdziwe, o tyle "" ~ "a+" już nie. Plus jest znakiem, który w jednych programach jest znakiem specjalnym (czyli "a+" oznacza niepusty ciąg liter "a"), a w innych zwykłym (czyli "a+" to dwa znaki: "a" i "+", a niepusty ciąg "a" jest oznaczany przez "a\+"). Przykłady:

aaaaaa ~ a\+ <sed>
aaaaaa ~ a+ <awk>
xaaaay ~ xa+y
xy !~ xa+y (mimo że xy ~ xa*y)

Dalej: czasem zachodzi potrzeba podania kilku różnych znaków, które mogą wystąpić w jednym miejscu - kropka może być zbyt ogólna (bo nas np. satysfakcjonują tylko litery c, h i x). Do takich alternatyw można używać nawiasów "[" i "]". W miejsce tego fragmentu z nawiasami może zostać podstawiona jedna z liter wystepujących między nimi. Przykłady:

a ~ [ab] <sed>
b ~ [ab]
c !~ [ab]
yyyaxxx ~ [xy]*a[xy]*

Ostatni przykład jest już troszkę bardziej skomplikowany: jak widać, całe wyrażenie nawiasowe jest traktowane jak jedna litera: "[xy]*" oznacza "dowolny ciąg złożony z liter x i y". W tym wypadku po takim ciągu następuje litera a, a potem znowu ciąg liter x i y.

Czasem łatwiej jest powiedzieć, jakich znaków nie chcemy w danym miejscu, niż jakie chcemy. Używa się podobnego wyrażenia nawiasowego, z dodatkowym symbolem "^" w środku: np. "[^ab]" oznacza dowolną literę, poza a i b. Przykłady:

abcd ~ [^xy]*
abxcd !~ [^xy]*
abcd ~ [ab][ab][^x]*

W ostatnim przykładzie do wzorca pasują wszystkie ciągi, które jako dwa pierwsze znaki mają a lub b, a potem dowolny ciąg znaków, w którym ani razu nie wystepuje x.

W nawiasach klamrowych można też używać zakresów znaków: np. [a-z] oznacza literki od "a" do "z". "[a-ce-zA-Z]" oznacza wszystkie małe litery oprócz litery d, oraz wszystkie duże litery.

Jeśli jakiś ciąg ma pasować do jednego z kilku możliwych wyrażeń, realizuje się to za pomocą łącznika "lub": "|" ("rurka" (ang. pipe)). Tak samo jak przy plusie, rurka czasem jest znakiem zwykłym, czasem specjalnym. Przykłady:

aa ~ aa\|bb <sed>
bb ~ aa\|bb
aa ~ aa|bb <awk>
abcd ~ ab*cd|x*y

Rurka jest znakiem globalnym - tzn. nie można za jej pomocą prosto uzyskać np. kombinacji: "najpierw ab lub cd, a potem ef lub gh". W takich sytuacjach trzeba użyć grupowania za pomocą nawiasów "(" i ")". Te nawiasy pozwalają traktować część wyrażenia tak jakby to była jedna litera. Podobnie jak "|" nawiasy mogą być jako zwykłe lib specjalne:

abef ~ \(ab\|cd\)\(ef\|gh\) <sed>
abef ~ (ab|cd)(ef|gh) <awk>
ababab ~ (ab)*
axbxcx ~ ([abc]x)*

Wyrażenie, które ma dopasować dokładnie 7 literek "a" może wyglądać tak: "aaaaaaa". Ale jeśli w ten sam sposób będziemy chcieli wyłapać 120 literek "a", może to wyglądać dość pokracznie. Dlatego część (nie wszystkie) programów udostępnia specjalne nawiasy klamrowe "{" i "}". Wyrażenie w postaci "a{3}" oznacza 3 literki a. "a{3,}" oznacza trzy lub więcej takich literek. "a{3,6}" oznacza od 3 do 6 liter "a". Nawiasy klamrowe czasem muszą być, do czego się już wszyscy zdążyli przyzwyczaić :), poprzedzone znaczkiem "\":

assb ~ as\{2\}b <sed>
assssssssssb !~ as\{2\}b
assssssssssb ~ as\{2,\}b
assssssssssb !~ as\{2,3\}b assb ~ as{2}b <awk w trybie zgodności z POSIXem>

Są dwa znaki specjalne, na ogół nie poprzedzane, oznaczające początek i koniec linijki: "^" oznacza początek wiersza (przed pierwszym znakiem), a "$" koniec. Czyli jeśli chcemy znaleźć wszystkie linijki składające się tylko z jednego znaku, wystarczy poszukać wyrażenia "^.$".

Z ciekawostek jeszcze występują takie rzeczy jak klasy znaków (aczkolwiek nie wszędzie): zamiast pisać [0-9a-zA-Z_], co oznacza z grubsza znaki występujące w słowach, można użyć [[:alnum:]] - to jest to samo. Zamiast znaków białych: [ \t] można użyć [[:space:]]. Itd.