Wyrażenia regularne (regular expressions, regex) są używane przez wiele programów wyszukujących określone ciągi znaków w tekstach.
Technicznie wyrażenie regularne jest zbiorem zasad, które ciąg znaków powinien spełnić. Jeśli ciąg znaków spełnia podane zasady, mówimy, że pasuje do wyrażenia regularnego (ang. to match). Z matematycznego punktu widzenia wyrażenie regularne definiuje pewien (zazwyczaj nieskończony) zbiór ciągów znaków (elementy zbioru pasują do tego wyrażenia).
Można zaobserwować podobieństwa między wzorcami nazw plików, a wyrażeniami regularnymi. Oba spełniają podobne zadania, przy czym wzorce nazw zazwyczaj są stosowane w miejscach, gdzie operujemy na nazwach plików. Podstawowe wzorce nazw plików zdefiniowane w POSIX funkcjonalnie są podzbiorem wyrażeń regularnych, choć używają innej składni.
W przeciwieństwie do wzorców, wyrażenia regularne nie są interpretowane przez powłokę. Każdy program, w którym można stosować wyrażenia regularne, zawiera wewnętrznie obsługę tego mechanizmu (potencjalnie z drobnymi rozszerzeniami lub różnicami).
Przykładami programów, które z nich korzystają, są grep i less.
Informacje o wyrażeniach regularnych można znaleźć w manualach:
$ man 1 grep
$ man 7 regex
Składnia wyrażeń regularnych, <r>
oznacza zagnieżdżone wyrażenie regularne:
Składnia | Znaczenie |
---|---|
(<r>) |
grupowanie wyrażeń za pomocą nawiasów |
^ |
początek ciągu/linii |
$ |
koniec ciągu/linii |
. |
dowolny pojedynczy znak niebędący nową linią |
[ab] |
dowolny jeden znak ze zbioru znaków pomiędzy [] |
[a-z] |
dowolny (jeden) znak z zakresu |
[^ab] |
^ oznacza dopełnienie zbioru. Dowolny znak z dopełnienia podanego zbioru (tu: nie a i nie b) |
<r>* |
Wyrażenie <r> powtórzone 0 lub więcej razy |
<r>+ |
Wyrażenie <r> powtórzone 1 lub więcej raz |
<r>? |
Wyrażenie <r> powtórzone 0 lub 1 raz |
<r>{n,m} |
Wyrażenie |
<r>{,m} |
Podobnie: od 0 do m razy |
<r>{n,} |
co najmniej n razy |
<r1>|<r2> |
Alternatywa: jedno z wyrażeń <r1> lub <r2> |
\<znak> |
w przypadku znaków o specjalnym znaczeniu wprowadza dosłownie ten znak (tak samo jak w shellu). Ponadto niektórym znakom nadaje inne znaczenie, przykłady pod tabelą. |
<zwykły znak> |
dokładnie jedno wystąpienie tego znaku |
Przykłady specjalnych sekwencji z \
Sekwencja | znaczenie |
---|---|
\d |
dowolna cyfra |
\t |
tabulator |
\b |
granica słowa (pusty ciąg pomiędzy białym znakiem i niebiałym znakiem lub końcem ciągu a niebiałym znakiem) |
Przykłady wyrażeń regularnych:
a*
– ciąg składający się z dowolnej liczby liter aa.*
– ciąg zaczynający się literą a\.
– dokładnie jeden znak – kropka(ab|cd)*
– ciąg składający się z dowolnej liczby powtórzeń ab lub cd, pasują takie ciągi, jak:
<ciąg pusty>
(ab)*|(cd)*
– dowolnie dużo powtórzeń ab lub cd (ale nie mieszanych)
<ciąg pusty>
[^a]*
– ciąg nie zawierający litery a (również pusty)..{3,5}
– ciąg od 3 do 5 dowolnych znaków.
Przypomnienie: less pozwala na wyszukiwanie ciągu w przeglądanym tekście. Dotychczas na zajęciach było podawane, że /słowo – wyszukuje słowo n – przechodzi do kolejnego wystąpienia N – przechodzi do poprzedniego wystąpienia. Było to uproszczenie. W rzeczywistości nie podajemy słowa, a wyrażenie regularne.
/.
oczywiście wyszuka wszystkie pojedyncze znaki, czyli nie tylko kropki)/\b[A-Z]+\b
\b
)^
$
{n,m}
W najprostszym wariancie grep
pozwala na wyszukiwanie linii zawierających podane słowo. grep
można użyć zarówno do przeszukiwania plików podanych jako argument, jak i jako filtra z operatorem |
$ grep uszatek /etc/passwd
$ # Wyświetli wszystkie linie zawierające słowo uszatek
$ # z pliku /dev/passwd.
Z opcją -E
grep wyszukuje wyrażenia regularnego zamiast słowa.
$ grep -E 'regex' plik plik2 .. # wyszukuje linii w plikach
$ polecenie | grep -E 'regex' # filtruje wyjście innego programu
Uwaga! Aby zapobiec interpretowaniu znaków specjalnych przez powłokę, wyrażenie regularne należy ZAWSZE podawać w ' '
.
Przykłady:
$ grep -E 'root|nobody' /etc/passwd
Jak wyszukać linie niezawierające litery o?
$ grep -E '[^o]*' /etc/passwd # ŻLE!
grep poszukuje linii, które zawierają ciąg spełniający wyrażenie regularne. Do [^o]* pasuje ciąg pusty. Każda linia zawiera ciąg pusty, zatem grep wypisze wszystkie linie. Należy użyć dopasowania do początku i końca linii:
$ grep -E '^[^o]*$' /etc/passwd # Dobrze
Do testowania ćwiczeń z wyrażeń regularnych warto używać systemowego pliku słownika w /usr/share/cracklib/cracklib-small
(w przypadku komputerów w lab) albo /usr/share/dict/words
(w wielu innych systemach). Jak ktoś nie ma żadnego z podanych słowników, to proszę o kontakt indywidualnie, to doinstalujemy go w danym systemie.
$ grep -E 'regex' /usr/share/cracklib/cracklib-small
Ćwiczenia wykonać na podanym wyżej pliku. Wykorzystać fakt, że w każdej linii jest dokładnie jedno słowo.
Napisać wyrażenie regularne do grep -E
, które:\
Znajdzie wszystkie słowa zawierająca przynajmniej jedno a i jedno b (w dowolnej kolejności).
Znajdzie wszystkie słowa zaczynające się na a i kończące na b.
Znajdzie wszystkie słowa pięcioliterowe.
Znajdzie wszystkie słowa o długości powyżej 5 liter.
Znajdzie wszystkie słowa zaczynające się wielką literą.
Znajdzie wszystkie słowa zawierające dokładnie 3 litery a.
Znajdzie wszystkie słowa zawierające w sobie cat lub dog, ale nie samo cat i dog.
(To testować na /etc/resolv.conf
i /etc/sensors3.conf
) Znajdzie wszystkie linie zawierające liczbę rzeczywistą w formacie (cyfry).(cyfry)
kropka z częścią dziesiętną może być pominięta. Adres IPv4 nie jest oczywiście liczbą i nie należy go wyświetlać.
Wyrażenia regularne są ważne. I powrócą na któryś z kolejnych zajęć.