Wyrażenia regularne

Wyrażenia regularne

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 POSIX

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 powtórzone od n do m razy (włącznie)
<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:

Ćwiczenia

less

  1. Wyszukiwanie z less

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.

  • otworzyć podręcznik do ls (lub innego ulubionego programu)
  • wyszukać wszystkie kropki występujące w tym manualu (/. oczywiście wyszuka wszystkie pojedyncze znaki, czyli nie tylko kropki)
  • wyszukać w manualu wszystkie słowa pisane WIELKIMI LITERAMI: /\b[A-Z]+\b
    (przykład użycia granicy słowa – \b)
  • wyszukać wszystkie linie zawierające nie więcej niż 20 znaków Podpowiedź: użyć następujących elementów ^ $ {n,m}
  • wyszukać wszystkie nawiasy kwadratowe Podpowiedź: podobnie jak kropka, nawiasy kwadratowe też mają specjalne znaczenie.

grep

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

Ć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:\

  1. Znajdzie wszystkie słowa zawierająca przynajmniej jedno a i jedno b (w dowolnej kolejności).

  2. Znajdzie wszystkie słowa zaczynające się na a i kończące na b.

  3. Znajdzie wszystkie słowa pięcioliterowe.

  4. Znajdzie wszystkie słowa o długości powyżej 5 liter.

  5. Znajdzie wszystkie słowa zaczynające się wielką literą.

  6. Znajdzie wszystkie słowa zawierające dokładnie 3 litery a.

  7. Znajdzie wszystkie słowa zawierające w sobie cat lub dog, ale nie samo cat i dog.

  8. (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ć.

Ciąg dalszy

Wyrażenia regularne są ważne. I powrócą na któryś z kolejnych zajęć.