Wyrażenia regularne to napisy, które pozwalają na opis składni tekstu. Ich zadaniem jest ułatwienie przetwarzania napisów, w szczególności stwierdzania zgodności z podanymi regułami, wyszukiwania oraz wyznaczania fragmentów. Przewodnik ten omawia składnię wyrażeń regularnych określane mianem PCRE (ang. Perl Compatible Regular Expressions) w języku PHP.
Wyrażenia regularne opisane w artykule będę testował wykorzystując funkcję preg_match():
$wynik_funkcji = preg_match(
wyrazenie_regularne,
badany_napis
);
Funkcja ta posiada dwa obowiązkowe parametry: wyrazenie_regularne oraz badany_napis. Odpowiada ona na pytanie: czy podane wyrażenie występuje w napisie. Jeśli wywołasz funkcję preg_match() z parametrami:
$w = preg_match(’/abc/’, 'abecadło’);
wówczas funkcja ta sprawdzi, czy w napisie abecadło występuje wyrażenie regularne abc.
Wyrażenie regularne przekazywane do funkcji preg_match() jest ujęte w ograniczniki. Rolę ograniczników może spełniać znak niebędący literą ani cyfrą. Zazwyczaj stosowane są ukośniki /, ale może to także być m.in. , *, # czy nawiasy ( ), [ ] oraz { }.
Jeśli chcesz szukać napisu rudy 102, to wyrażenie przekazywane do funkcji preg_match() przyjmie postać /rudy 102/. Gdy jako ograniczniki zastosujesz znaki , wówczas wyrażenie regularne będzie wyglądało: |rudy 102|. Oto kilka różnych ograniczników:
/rudy 102/
rudy 102
*rudy 102*
#rudy 102#
,rudy 102,
[rudy 102]
{rudy 102}
Wynikiem funkcji jest zawsze liczba 0 lub 1, która informuje o tym, czy podane wyrażenie zostało odnalezione. Czyli po wywołaniu:
$w = preg_match(’/abc/’, 'abecadło’);
zmienna $wyn przyjmie wartość 0 (napis abc nie występuje w napisie abecadło). Natomiast po wywołaniu:
$w = preg_match(’/a/’, 'ala ma kota’);
w zmiennej $wyn znajdzie się liczba 1, która oznacza, że wyrażenie regularne a występuje w napisie ala ma kota. Zwróć uwagę, że litera a występuje w napisie „ala ma kota” cztery razy, zaś funkcja preg_match() zwraca wynik równy 1. Dzieje się tak dlatego, że funkcja ta kończy działanie po odnalezieniu pierwszego wystąpienia.
Nie ma również znaczenia miejsce dopasowania wyrażenia. Wyszukiwane wyrażenie może wystąpić na początku (tak jak w przykładzie z napisem „ala ma kota”) lub na końcu. Wywołanie:
$w = preg_match(’/s/’, ’ adres ’);
zwróci wartość 1. Oznacza to, że wyrażenie regularne s zostało odnalezione w napisie adres. Innymi słowy funkcja preg_match() przeprowadza test: czy dane wyrażenie występuje co najmniej jeden raz w podanym napisie? Odpowiedzią na to pytanie jest zawsze liczba 0 (wyrażenie nie wystąpiło) lub 1 (wyrażenie wystąpiło co najmniej jeden raz na dowolnej pozycji).
Znaki
Wyrażenia regularne mogą zawierać litery, cyfry, znaki interpunkcyjne oraz znaki niewidzialne. Litery, cyfry i wszystkie znaki, które nie mają specjalnego znaczenia, są dopasowywane w zwykły sposób. Na przykład wyrażenie a4 pasuje do napisu a4:
$w = preg_match(’/a4/’, '…format a4…’);
Polskie znaki diakrytyczne mogą wystąpić zarówno w wyrażeniu regularnym, jak i w badanym napisie w kodzie iso-8859-2 lub windows-1250:
$w = preg_match(’/żół/’, '…żółty żółw…’);
Wyrażenia regularne, podobnie jak napisy w PHP, pozwalają na korzystanie z kilku znaków specjalnych oraz ze znaków o zadanych kodach ASCII. Pełne zestawienie zawiera tabela 1. Zwróć uwagę, że znaki te w części pokrywają się z zestawem znaków specjalnych PHP. Może to prowadzić do nieporozumień!
Cudzysłowy a apostrofy w PHP
Uwaga: PHP i wyrażenia regularne stosują identyczną metodę zapisu znaków specjalnych i znaków o zadanych kodach ASCII. Pamiętaj, że napisy w PHP ujęte w cudzysłów podlegają interpretacji, zaś napisy ujęte w apostrofy nie podlegają interpretacji.
Musisz dobrze zrozumieć różnicę w działaniu następujących instrukcji PHP:
echo „\n”;
echo '\n’;
W pierwszej z wymienionych instrukcji sekwencja dwóch znaków \n zostanie zamieniona — przez parser PHP — na jeden znak ASCII o kodzie x0a. Pierwszy z napisów ma więc długość 1 (tj. zawiera jeden znak). W przypadku drugiego napisu (ujętego w apostrofy) taka interpretacja nie ma miejsca. Napis ten składa się z dwóch znaków: \ oraz n.
Te same zasady dotyczą znaków o zadanym kodzie:
echo „\x48”;
echo '\x48′;
Chcąc zbadać wystąpienie znaku nowej linii \n możesz wykorzystać wyrażenie regularne \n:
$w = preg_match(’/\n/’, „\x0a”);
Wyrażenia: '/\n/’ oraz „/\n/” nie są — w języku PHP — równoważne. Użycie cudzysłowu ” powoduje, że znak \n zostanie zinterpretowany przez PHP. Do funkcji preg_match() trafia wówczas jeden znak ASCII o kodzie x0a. Jeśli natomiast wykorzystane są znaki ’, to do funkcji preg_match() trafia napis dwuznakowy \n (PHP nie interpretuje znaków specjalnych wewnątrz apostrofów ’). Interpretacja dwuznakowego napisu \n jako znaku nowego wiersza należy wówczas do funkcji preg_match().
Do wyszukiwania znaku nowego wiersza możesz również użyć wyrażenia „/\n/” (zapisanego w cudzysłowach):
$w = preg_match(„/\n/”, „\x0a”);
W takiej sytuacji, do funkcji preg_match() trafia jeden bajt o kodzie x0a (PHP zamienia „\n” na jeden bajt: znak złamania wiersza).
Znaki o zadanych kodach ASCII możesz przekazywać do wyrażeń regularnych stosując notację: \xHH, gdzie HH jest kodem szesnastkowym szukanego znaku. Również tutaj należy pamiętać o różnicy pomiędzy znakami ’ oraz ” w PHP. Wywołanie:
$w = preg_match(’/\x07/’, „\x07”);
zakończy się sukcesem: w jednobajtowym napisie (ujętym w cudzysłów) szukamy jednego bajtu o kodzie 07. Bajt ten jest przekazany do preg_match() jako napis \x07. Natomiast wywołanie:
$w = preg_match(’/\x07/’, '\x07′);
zwróci wynik 0. W napisie składającym się z czterech znaków '\x07′ szukamy znaku ASCII o kodzie 07.
Wyłączanie interpretacji znaków specjalnych
Komendami \Q oraz \E można wyłączyć i ponownie włączyć interpretację znaków specjalnych. Komenda \Q wyłącza, natomiast \E włącza interpretację znaków specjalnych z tabeli 1.
Podany w wyrażeniu regularnym:
$w = preg_match(’/.\Q\n/’, ’ a\nb’);
napis \n będzie pasował do dwóch znaków \n, a nie do jednego znaku o kodzie ASCII x0a.
Warto zwrócić uwagę na analogię do języka PHP. W PHP interpretację znaków specjalnych wyłączamy korzystając z apostrofów. Natomiast włączamy stosując cudzysłowy.
Metaznaki i cytowanie metaznaków
Część znaków ma w wyrażeniach regularnych specjalne znaczenie. Znakami tymi są:
\ . [ ^ $ ( ) * + ? {
oraz stosowane ograniczniki (np. /). Z racji na swoje specjalne znaczenie znaki te są nazywane metaznakami. Zauważ, że na liście tej nie ma dwóch nawiasów zamykających ] oraz }.
Jeśli znaki te chcesz użyć, pozbawiając je ich specjalnego znaczenia, to musisz poprzedzać je znakiem backslash \. Jest to określane w angielskich dokumentach jako escaping, zaś znak \ jest nazywany escape character. W polskich książkach i artykułach pojawiają się określenia ucieczka, czy znak ucieczki. Ja będę stosował termin cytowanie.
Chcąc szukać ogranicznika / powinieneś użyć wyrażenia /\//:
$w = preg_match(’/\//’, ’’);
Wyrażenie \.\* pasuje do napisu .*:
$w = preg_match(’/\.\*/’, ’ obraz.* ’);
Zauważ, że szukając znaku \ musisz użyć — bez względu na to, czy stosujesz znaki ’ czy ” w PHP — czterech znaków \:
$w = preg_match(’/\\\\/’, 'a\nb’);
To jest pewien paradoks, którego powodem jest to, że PHP i wyrażenia regularne stosują znak \ do usuwania specjalnego znaczenia następującego po nim znaku. Jeśli chcesz wydrukować znak \, to musisz go podwoić:
echo „\\”;
Na tej samej zasadzie musisz podwajać znaki \ przekazywane do wyrażeń regularnych. Zatem napis przekazany do wyrażenia regularnego musi mieć podwojony każdy znak \ gdyż:
- jest napisem w PHP,
- jest przekazywany do wyrażenia regularnego.
Podwojenie podwajania da właśnie cztery znaki \ w miejsce jednego. Jeśli zastosujesz wyrażenie regularne w zapytaniu SQL zapisanym w postaci napisu w PHP, to w miejsce każdego znaku \ będziesz musiał użyć … ośmiu znaków \! Dlaczego? Trzykrotne podwajanie to nic innego jak 2 do trzeciej potęgi.
Komendy \Q oraz \E dotyczą nie tylko znaków \n czy \r, ale także mataznaków. Wyrażenie:
\Q^$
jest równoważne wyrażeniu:
\^\$
Paradoks ośmiu backslashy
PHP, SQL oraz wyrażenia regularne stosują ten sam znak \ do usuwania specjalnego znaczenia. Jeśli zatem chcemy na ekranie wydrukować znak \, należy — w PHP — użyć podwojonego znaku:
echo „\\”;
Jeśli znak backslash zechcesz umieścić w zapytaniu SQL, to w miejsce jednego \ będziesz musiał użyć czterech znaków \:
$query = „
SELECT
*
FROM
tznaki
WHERE
znak = '\\\\’
„;
Jeżeli dodatkowo zapytanie SQL zawiera wyrażenie regularne, to w miejsce jednego \ wystąpi aż osiem znaków \:
$q = „
SELECT
*
FROM
tparadoks
WHERE
napis REGEXP '\\\\\\\\’
„;
Kropka — dowolny znak
Kropka pasuje do dowolnego znaku. Wyrażenie regularne:
a.c
pasuje do napisów:
aac
abc
acc
adc
aec
afc
…
Pomiędzy literami a oraz c musi wystąpić dokładnie jeden dowolny znak (może to być nie tylko litera, ale także cyfra, czy dowolny znak interpunkcyjny).
Wywołanie:
$w = preg_match(’/a.c/’, 'ac’);
zwróci wynik 0. Zaś:
$w = preg_match(’/a.c/’, '…a0c…’);
zwróci wynik 1, gdyż w napisie …a0c… występuje napis a0c pasujący do wyrażenia a.c.
Jedynym znakiem, który nie jest dopasowywany do kropki jest znak \n (chyba, że włączony jest tryb /s — o tym nieco dalej). Zatem wyrażenie a.c nie będzie pasowało do napisu …a\nc… (konieczne cudzysłowy w napisie!):
$w = preg_match(’/a.c/’, „…a\nc…”);
Oczywiście znak . pasuje również do kropki. Jeśli jednak chcesz szukać kropek, na przykład w nazwie pliku obraz.jpg, to pamiętaj o backslashu:
$w = preg_match(’/\.jpg/’, 'obraz.jpg’);
gdyż bez poprzedzenia kropki znakiem \, podane rozszerzenie .jpg będzie pasowało również do
ajpg
bjpg
cjpg
…
Pamiętaj również, o tym, że preg_match() odpowiada na pytanie: czy podane wyrażenie występuje w podanym napisie co najmniej jeden raz i na dowolnej pozycji. Zatem wyrażenie x.y zostanie dopasowane do napisów:
’x0y’
’ xxy ’
’one xay two xby three xxy’
W pierwszym dopasowywane wyrażenie odpowiada całemu napisowi, w drugim — występuje jeden raz w środku. A w trzecim występuje wiele razy.
Dopasowany napis
Funkcja preg_match() pozwala poznać napis dopasowany do wyrażenia. Jeśli wywołasz ją podając trzeci parametr:
$wynik_funkcji = preg_match(
wyrazenie_regularne,
badany_napis,
dopasowanie
);
to w zmiennej dopasowanie zostanie umieszczone odnalezione wyrażenie regularne. Na przykład po wywołaniu:
$w = preg_match(’/X.Y/’, 'X9Y XfY XXY’, $m);
w zmiennej $d zostanie umieszczony napis X9Y. Jest to pierwsze wystąpienie wyrażenia regularnego X.Y w zadanym napisie. Wszystkich wystąpień jest trzy:
X9Y
XfY
XXY
ale funkcja preg_match() kończy działanie po odnalezieniu pierwszego wystąpienia. Parametr $m pozwala na stwierdzenie, który fragment napisu pasuje do wyrażenia regularnego.
Zbiór znaków
Nawiasy kwadratowe służą do dopasowywanie liter z podanego zbioru. Wyrażenie:
[abc]
pasuje do jednej z liter a, b lub c.
O ile wywołanie:
$w = preg_match(’/abc/’, '…a…’);
wyszukuje wystąpienie trzyliterowego napisu abc, to wyrażenie regularne [abc]:
$w = preg_match(’/[abc]/’, '…a…’);
wyszukuje wystąpienie jednoliterowego napisu a lub jednoliterowego napisu b lub jednoliterowego napisu c.
Wyrażenie:
abc[012]def
pasuje do trzech napisów:
abc0def
abc1def
abc2def
Wewnątrz nawiasów kwadratowych możesz stosować zakresy znaków. Zbiór [a-d] oznacza małe litery a, b, c oraz d. Wyrażenie:
X[a-d]Y
pasuje do czterech napisów:
XaY
XbY
XcY
XdY
Zbiór znaków możemy wymienić w wyrażeniu kilkukrotnie. Wyrażenie:
X[a-dP-R4-8]Y
pasuje do napisów:
XaY XbY XcY XdY
XPY XQY XRY
X4Y X5Y X6Y X7Y X8Y
Zakresy mogą przeplatać się z pojedynczymi znakami. Wyrażenie:
1[e-gAF3-5QW]2
pasuje do napisów
1e2 1f2 1g2
1A2 1F2
132 142 152
1Q2 1W2
Jeśli w zbiorze chcesz umieścić znak -, to musisz go zacytować. Wyrażenie:
X[1\-9]Y
pasuje do napisów:
X1Y
X-Y
X9Y
Wewnątrz nawiasów kwadratowych należy cytować znaki -, backslash \, znak ^ (o ile występuje jako pierwszy) oraz nawias zamykający ]. Pozostałych znaków (ich znaczenie zostanie wyjaśnione poniżej) mających specjalne znaczenie w wyrażeniach regularnych, czyli np. +, *, (, ), {, }, ^ czy $ nie cytuj wewnątrz nawiasów kwadratowych. Wyrażenie:
X[a+*(){}^$b]Y
pasuje do napisów:
XaY X+Y X*Y X(Y X)Y
X{Y X}Y X^Y X$Y XbY
Dopełnienie zbioru znaków
Jeśli pierwszym znakiem w nawiasach kwadratowych jest karet ^:
[^a-f]
to podane wyrażenie pasuje do dokładnie jednej litery spoza wymienionego zbioru. Zatem wyrażenie:
X[^a-f]Y
pasuje do napisów:
XgY XhY XiY …
X0Y X1Y X2Y …
Pomiędzy literami X oraz Y nie może znaleźć się litera a, b, c, d ani f. Wyrażenie to nie pasuje do napisów:
XaY XbY XcY XdY XeY
Negacja może dotyczyć bardziej skomplikowanego zbioru. Wyrażenie:
X[^a-cTY3-8m.^]Y
nie pasuje do napisów:
XaY XbY XcY
XTY XYY
X3Y X4Y X5Y X6Y X7Y X8Y
XmY X.Y X^Y
Natomiast pasuje ono do wszystkich pozostałych trójek X Y (w środku pomiędzy X oraz Y musi wystąpić dokładnie jeden znak spoza wymienionego zbioru [^a-cTY3-8m.^]).
Klasy znaków
Niektóre grupy znaków, takie jak litery, cyfry, czy białe znaki, są bardzo często wykorzystywane. W celu ułatwienia posługiwania się nimi, dla najbardziej popularnych grup, wprowadzono notację skróconą. Tabela 2 zawiera pełne zestawienie.
Wyrażenie regularne:
a\sb\sc
pasuje do napisów a b c w których, pomiędzy literami a, b oraz c występują dowolne (ale pojedyncze!) białe znaki.
Natomiast wyrażenie:
\w\s\d\s\w
pasuje między innymi do napisów:
a 1 a
a\t9\nX
Q\n7\nR
_ 0 _
(najpierw musi wystąpić znak ze zbioru [a-zA-Z0-9_], następnie biały znak ze zbioru [¤\n\r\f\t], jako trzeci znak — cyfra [0-9], po niej biały znak, a na końcu ponownie znak ze zbioru [a-zA-Z0-9_]).
W równie prosty sposób wykorzystujemy zbiory pisane dużą literą (\W, \D, \S). Zbiory te reprezentują dopełnienia zbiorów \w, \d oraz \s. Wyrażenie:
\S\s\S
pasuje do napisów
a b
0\t9
-\n*
(pomiędzy dwoma znakami, które nie są znakami białymi powinien wystąpić dokładnie jeden znak biały). Oczywiście w PHP znak \ należy — wewnątrz napisów — podwajać. Jednak z racji na to, że napisy \w, \s czy \d nie mają żadnej interpretacji w PHP, wyrażenia:
\S\s\S
\w\s\d
mogą być pisane z pojedynczym znakiem \.
POSIX-owe klasy znaków
Ostatnim sposobem zapisu grupy znaków jest stosowanie tzw. POSIX-owych klas znaków. Tabela 3 zawiera pełne zestawienie klas POSIX-owych.
Wyrażenie:
a[[:alpha:]]a
pasuje do napisów, w których pomiędzy literami a występuje dowolna litera, np.:
aXa
aQa
Zaś wyrażenie:
a[^[:digit:]]a
pasuje do napisów, w których pojawiają się dwie litery a oddzielone znakiem różnym od cyfry, czyli np.:
aaa
a.a
a/a
Nazwy POSIX-owe mogą wystąpić — w jednym zbiorze — wielokrotnie. Na przykład wyrażenie:
[[:upper:][:digit:]]
pasuje do jednego znaku: dużej litery lub cyfry.