Środowisko programisty/Wyrażenia regularne: Różnice pomiędzy wersjami
→expr: Pierwsza wersja |
→Ćwiczenia: Dodanie rozwiązania |
||
(Nie pokazano 6 wersji utworzonych przez 2 użytkowników) | |||
Linia 1: | Linia 1: | ||
== Po co są wyrażenia regulrane? == | == Po co są wyrażenia regulrane? == | ||
Częstym zadaniem, które wykonuje się podczas pracy z | Częstym zadaniem, które wykonuje się podczas pracy z komputerem jest wyszukiwanie danego fragmentu tekstu w jakimś pliku, zmienianie go, sprawdzanie, czy dany tekst się w tym pliku znajduje. Generalnie potrzebujemy narzędzi do znajdywania wzorca o zadanej charakterystyce w jakimś większym tekście. Tą charakterystykę musimy jakoś podawać. W najprostszym przypadku podajemy po prostu tekst, jaki chcemy znaleźć. Na przykład, aby znaleźć wszystkie linie w pliku <code>tekst.txt</code>, w których znajduje się fragment <code>est</code>, wystarczy wykonać polecenie | ||
grep est tekst.txt | grep est tekst.txt | ||
Wtedy dla pliku | Wtedy dla pliku <code>tekst.txt</code> o zawartości | ||
To jest plik tekstowy. | To jest plik tekstowy. | ||
Testujemy narzędzie grep. | Testujemy narzędzie grep. | ||
Linia 13: | Linia 13: | ||
Testujemy narzędzie grep. | Testujemy narzędzie grep. | ||
To jest piąta linia. | To jest piąta linia. | ||
Zamiast wyszukiwać | Zamiast wyszukiwać daną frazę, możemy chcieć znaleźć bardziej skomplikowane wzorce, jak na przykład wszystkie adresy email, które znajdują się w danym tekście, czy też wszystkie formy słowa pies. W tych celach potrzebne są bardziej zaawansowane możliwości podawania charakterystyki frazy, której chcemy szukać. Taką możliwość dają właśnie wyrażenia regularne. | ||
== Składnia == | == Składnia == | ||
Linia 19: | Linia 19: | ||
=== Znaki === | === Znaki === | ||
Najprostsze wyrażenia regularne składają się z ciągu znaków. Niektóre znaki są specjalne, więc aby rozpoznać któryś z nich, trzeba poprzedzać go backslashem. Następujące znaki są specjalne: <code>.</code> <code>^</code> <code>$</code> <code>*</code> <code>?</code> <code>[</code> <code>]</code> <code>\</code>. Na przykład, aby wyszukać w tekście frazę <code>\abc[?]</code> używamy wyrażenia <code>\\abc\[\?\]</code>. | |||
=== Klasy znaków === | === Klasy znaków === | ||
Przypuśćmy, że chcemy | Przypuśćmy, że chcemy znaleźć wystąpienie jednego z łańcuchów <code>psa</code>, <code>psu</code> i <code>psy</code>. Zatem pierwsze dwa znaki są ustalone i są to <code>ps</code>. Natomiast przy trzecim znaku mamy pewną dowolność. Chcemy, aby trzeci znak mógł być równy <code>a</code>, <code>u</code> lub <code>y</code>. Do tego służą klasy znaków. Listę znaków, którą chcemy rozpoznać, umieszczamy pomiędzy nawiasami kwadratowymi: <code>[auy]</code>. Nasze wyrażenie wygląda więc tak: <code>ps[auy]</code>. | ||
bashtest@host:~$ grep ps[auy] <<KONIEC | bashtest@host:~$ grep ps[auy] <<KONIEC | ||
> psami | > psami | ||
Linia 34: | Linia 34: | ||
bashtest@host:~$ | bashtest@host:~$ | ||
W klasach możemy | W klasach możemy podawać przedziały znaków używając myślnika. Na przykład <code>[0-9]</code> rozpoznaje dowolną cyfrę. Możemy podawać kilka przedziałów: <code>[a-zA-Z]</code> rozpozna dowolną literę angielską. | ||
Jeśli umieścimy znak | Jeśli umieścimy znak <code>^</code> na początku opisu klasy, będzie to oznaczać rozpoznawanie wszystkich znaków nie znajdujących się na liście. Na przykład <code>[^xX]</code> oznacza dowolny znak różny od <code>x</code> i <code>X</code>. Oczywiście możemy podawać też przedziały. | ||
Jeśli chcemy umieścić na liście znak | Jeśli chcemy umieścić na liście znak <code>]</code>, należy go podać jako pierwszy na liście. Aby umieścić <code>^</code>, należy go umieścić w dowolnym miejscu, byle nie na początku. Wreszcie, aby umieścić <code>-</code>, należy go umieścić na końcu listy. | ||
Ponadto mamy możliwość kojarzenia dowolnego znaku oprócz znaku końca linii. Do tego służy znak specjalny | Ponadto mamy możliwość kojarzenia dowolnego znaku oprócz znaku końca linii. Do tego służy znak specjalny <code>.</code> (kropka). | ||
=== Powtórzenia === | === Powtórzenia === | ||
Znak specjalny | Znak specjalny <code>*</code> próbuje dopasować poprzedzający go element zero lub więcej razy w szukanym tekście. Na przykład <code>=*</code> dopasowuje się do ciągu znaków równości (być może ciągu pustego). | ||
Inny przykład, wyrażenie | Inny przykład, wyrażenie <code>[a-zA-Z][a-zA-Z0-9_]*</code> reprezentuje identyfikator, tzn. niepusty łańcuch znaków zaczynający się od litery i składający się z liter, cyfr oraz znaków podkreślenia. | ||
Znak specjalny | Znak specjalny <code>+</code> jest podobny do <code>*</code> z tym, że próbuje on dopasować poprzedzający go element jeden lub więcej razy. | ||
Znak specjalny | Znak specjalny <code>?</code> mówi, że poprzedzający element może wystąpić, bądź też nie. Na przykład wyrażenie <code>-?[0-9]+</code> oznacza dowolny niepusty ciąg cyfr, być może poprzedzony znakiem <code>-</code>. Czyli takie wyrażenie może służyć do rozpoznawania dowolnej liczby całkowitej (jeżeli dopuścimy to, że zapis dziesiętny liczby może mieć wiodące zera). | ||
Ponadto ilość powtórzeń można podać bardziej w uniwersalny sposób: | Ponadto ilość powtórzeń można podać bardziej w uniwersalny sposób: | ||
Linia 56: | Linia 56: | ||
! symbol powtórzenia !! liczba powtórzeń poprzedzającego elementu | ! symbol powtórzenia !! liczba powtórzeń poprzedzającego elementu | ||
|- | |- | ||
| | | <code>{n}</code> || dokładnie <code>n</code> | ||
|- | |- | ||
| | | <code>{n,}</code> || co najmniej <code>n</code> | ||
|- | |- | ||
| | | <code>{,m}</code> || co najwyżej <code>m</code> | ||
|- | |- | ||
| | | <code>{n,m}</code> || co najmniej <code>n</code> i co najwyżej <code>m</code> | ||
|} | |} | ||
Linia 69: | Linia 69: | ||
Znaczniki pozycjonujące są takimi symbolami, które nie są kojarzone z żadnym ciągiem znaków, ale z pozycją w tekście spełniającą pewne ustalone warunki. | Znaczniki pozycjonujące są takimi symbolami, które nie są kojarzone z żadnym ciągiem znaków, ale z pozycją w tekście spełniającą pewne ustalone warunki. | ||
Jeśli chcemy, aby wzorzec był dopasowywany od początku linii, | Jeśli chcemy, aby wzorzec był dopasowywany od początku linii, wyrażenie zaczynamy znakiem <code>^</code>. Jeśli chcemy, aby wzorzec był dopasowywany do końca linii, wyrażenie kończymy znakiem <code>$</code>. Gdy użyjemy oba znaki, wzorzec będzie dopasowywany do całej linii. | ||
Na przykład, żeby znaleźć linie, w których znajduje się dokładnie jedna kropka możemy użyć wyrażenia | Na przykład, żeby znaleźć linie, w których znajduje się dokładnie jedna kropka, możemy użyć wyrażenia <code>^[^.]*\.[^.]*$</code>. Użycie <code>^</code> na początku i <code>$</code> na końcu oznacza, że zawsze będzie próba dopasowania całej linii. Następnie <code>[^.]*</code> oznacza dowolny ciąg znaków nie zawierający kropki, a <code>\.</code> oznacza wystąpienie kropki (trzeba użyć <code>\</code>, bo <code>.</code> jest znakiem specjalnym). | ||
Aby znaleźć | Aby znaleźć linię, która zaczyna się od dowolnej liczby spacji, liczby, później kropki, a następnie spacji i dużej litery, używamy wyrażenia <code>^ *[0-9][0-9]*\. *[A-Z]</code>. Za pomocą tego wyrażenia zostaną znalezione na przykład | ||
1. Wstęp | 1. Wstęp | ||
2.Wprowadzenie | 2.Wprowadzenie | ||
Linia 84: | Linia 84: | ||
^ 1. Wstęp | ^ 1. Wstęp | ||
Oprócz znaczników rozpoznających początek i koniec | Oprócz znaczników rozpoznających początek i koniec linii, mamy też szereg znaczników rozpoznających końce słowa: | ||
{| | {| | ||
| | | <code>\<</code> || znacznik mówiący, że w tym miejscu zaczyna się nowe słowo | ||
|- | |- | ||
| | | <code>\></code> || znacznik mówiący, że w tym miejscu kończy się słowo | ||
|- | |- | ||
| | | <code>\b</code> || znacznik mówiący, że w tym miejscu jest krawędź słowa (nie zaczyna ani nie kończy się żadne słowo) | ||
|- | |- | ||
| | | <code>\B</code> || znacznik mówiący, że w tym miejscu nie znajduje się krawędź słowa | ||
|} | |} | ||
Dokładne definicje co to jest brzeg | Dokładne definicje, co to jest brzeg słowa, są nieprzyjemnie skomplikowane i je pominiemy. Przyjrzyjmy się przykładowi. Dla frazy <code>Jola jest lojalna</code> następujące wyrażenia zostaną dopasowane do wycinka tej frazy: | ||
* | * <code>\<jest\></code> | ||
* | * <code>\bjest\b</code> | ||
* | * <code>\Best\b</code> | ||
* | * <code>\Bes\B</code> | ||
a poniższe wyrażenia nie zostaną dopasowane: | a poniższe wyrażenia nie zostaną dopasowane: | ||
* | * <code>\<est\></code> | ||
* | * <code>\best\b</code> | ||
* | * <code>\Bes\b</code> | ||
* | * <code>\>jest\<</code> | ||
=== Alternatywa === | === Alternatywa === | ||
Jeśli chcemy, aby był rozpoznawany jeden z dwóch możliwych napisów, to możemy użyć alternatywy | Jeśli chcemy, aby był rozpoznawany jeden z dwóch możliwych napisów, to możemy użyć alternatywy <code>|</code>, na przykład <code>jeden|dwa</code> rozpozna albo słowo <code>jeden</code> albo słowo <code>dwa</code>. Operator <code>|</code> może mieć jako argumenty też inne wyrażenia, na przykład <code>[0-9]*|[a-z]*|[A-Z]*</code> rozpoznaje albo ciąg cyfr, albo ciąg małych liter, albo ciąg dużych liter. | ||
=== Priorytety i nawiasowanie === | === Priorytety i nawiasowanie === | ||
Największy priorytet mają operatory powtórzenia. Jeśli chcemy mieć wyrażenie oznaczające powtórzenie większej liczby elementów, to możemy zrobić to poprzez stosowanie nawiasów | Największy priorytet mają operatory powtórzenia. Jeśli chcemy mieć wyrażenie oznaczające powtórzenie większej liczby elementów, to możemy zrobić to poprzez stosowanie nawiasów <code>( ... )</code>. Na przykład wyrażenie <code>([a-z][a-z])*</code> oznacza ciąg składający się z parzystej liczby małych literek. | ||
Najmniejszy priorytet ma alternatywa. Jeśli chcemy wyrażenie, które rozpoznaje | Najmniejszy priorytet ma alternatywa. Jeśli chcemy wpisać wyrażenie, które rozpoznaje wszystkie słowa, które rozpoczynają się od <code>Ta</code> lub od <code>Do</code>, to nie możemy napisać <code>Ta|Do[a-z]*</code>, gdyż takie wyrażenie rozpoznaje albo słowo <code>Ta</code>, albo ciąg literek zaczynający się od <code>Do</code>. Z pomocą przychodzą nawiasy: <code>(Ta|Do)[a-z]*</code>. | ||
=== Wyrażenia podstawowe i rozszerzone === | === Wyrażenia podstawowe i rozszerzone === | ||
Wyrażenia regularne dzielimy na podstawowe i rozszerzone. W wyrażeniach podstawowych znaki ?, +, {, |, (, ) nie mają specjalnego znaczenia, zamiast nich trzeba używać wersji z backslashem: \?, \+, \{, \|, \(, \). W | Wyrażenia regularne dzielimy na podstawowe i rozszerzone. W wyrażeniach podstawowych znaki <code>?</code>, <code>+</code>, <code>{</code>, <code>|</code>, <code>(</code>, <code>)</code> nie mają specjalnego znaczenia, zamiast nich trzeba używać wersji z backslashem: <code>\?</code>, <code>\+</code>, <code>\{</code>, <code>\|</code>, <code>\(</code>, <code>\)</code>. W wyrażeniach rozszerzonych możemy stosować wszystkie wymienione znaki specjalne. | ||
Rozróżnienie takie powstało na skutek różnych narzędzi i zachowania kompatybilności między nimi. Niektóre narzędzia używają składni z podstawowymi, a niektóre z rozszerzonymi wersjami wyrażeń regularnych. Przy omawianiu poszczególnych komend powiemy, które używają jakiej składni. | Rozróżnienie takie powstało na skutek różnych narzędzi i zachowania kompatybilności między nimi. Niektóre narzędzia używają składni z podstawowymi, a niektóre z rozszerzonymi wersjami wyrażeń regularnych. Przy omawianiu poszczególnych komend powiemy, które używają jakiej składni. | ||
Linia 125: | Linia 125: | ||
=== grep === | === grep === | ||
Polecenie | Polecenie <code>grep</code> jest podstawowym narzędziem do wyszukiwania wzorca w tekście z użyciem wyrażeń regularnych. <code>grep</code> używa podstawowych wyrażeń. Jeśli jest potrzeba użycia rozszerzonych wyrażeń można użyć wtedy w zastępstwie polecenia <code>egrep</code>. | ||
Podstawowe użycie, to | Podstawowe użycie, to | ||
Linia 135: | Linia 135: | ||
lub | lub | ||
grep wzorzec plik1 plik2 ... | grep wzorzec plik1 plik2 ... | ||
W przypadku jednego pliku, działanie jest takie samo jak bez argumentów z tą różnicą, że dane są czytane z pliku, a nie ze standardowego wejścia. | W przypadku jednego pliku, działanie jest takie samo jak bez argumentów, z tą różnicą, że dane są czytane z pliku, a nie ze standardowego wejścia. | ||
W wersji z wieloma plikami | W wersji z wieloma plikami wzorzec wyszukiwany jest w każdym pliku. Wyświetlane są linie zawierające wystąpienie wzorca poprzedzone nazwą pliku, w którym został on znaleziony. | ||
Ponadto jest wiele opcji kontrolujących sposób szukania i wyświetlane informacje. Omówimy najprzydatniejsze z nich. | Ponadto jest wiele opcji kontrolujących sposób szukania i wyświetlane informacje. Omówimy najprzydatniejsze z nich. | ||
; -e wzorzec : alternatywny sposób podania wzorca; przydatny, gdy wyrażenie regularne może zaczynać się od znaku - | ; <code>-e wzorzec</code> : alternatywny sposób podania wzorca; przydatny, gdy wyrażenie regularne może zaczynać się od znaku <code>-</code> | ||
; -i : ignoruje rozróżnianie wielkich liter, | ; <code>-i</code> : ignoruje rozróżnianie wielkich liter, | ||
; -c : zlicza tylko liczbę wystąpień wzorca, | ; <code>-c</code> : zlicza tylko liczbę wystąpień wzorca, | ||
; -w : dopasowuje wzorzec tylko do całych słów, | ; <code>-w</code> : dopasowuje wzorzec tylko do całych słów, | ||
; -x : dopasowuje wzorzec tylko do całych linii, | ; <code>-x</code> : dopasowuje wzorzec tylko do całych linii, | ||
; -v : odwraca sens dopasowania i wyszukuje tylko linie w których nie udało się dopasować wzorca, | ; <code>-v</code> : odwraca sens dopasowania i wyszukuje tylko linie w których nie udało się dopasować wzorca, | ||
; -q : nic nie wypisuje na standardowe wyjście i kończy działania na pierwszym dopasowaniu; przydatne, gdy polecenie grep chcemy użyć jako warunku. | ; <code>-q</code> : nic nie wypisuje na standardowe wyjście i kończy działania na pierwszym dopasowaniu; przydatne, gdy polecenie <code>grep</code> chcemy użyć jako warunku. | ||
grep zwraca zero jako kod wyjścia, gdy wzorzec uda się znaleźć i wartość niezerową w przeciwnym przypadku. Można to wykorzystać w połączeniu z opcją -q w instrukcjach warunkowych. Na przykład | <code>grep</code> zwraca zero jako kod wyjścia, gdy wzorzec uda się znaleźć i wartość niezerową w przeciwnym przypadku. Można to wykorzystać w połączeniu z opcją <code>-q</code> w instrukcjach warunkowych. Na przykład | ||
if grep -qw TODO opis_prac.txt; then | if grep -qw TODO opis_prac.txt; then | ||
echo " | echo "Zostało jeszcze coś do zrobienia" | ||
fi | fi | ||
Linia 161: | Linia 161: | ||
=== expr === | === expr === | ||
Polecenie expr oprócz obliczania wyrażeń arytmetycznych ma też podstawowe operacje na łańcuchach znakowych. Szczególnie przydatny jest operator | Polecenie <code>expr</code> oprócz obliczania wyrażeń arytmetycznych ma też podstawowe operacje na łańcuchach znakowych. Szczególnie przydatny jest operator <code>:</code>. Jego składnia to | ||
expr łańcuch : wzorzec | expr łańcuch : wzorzec | ||
Wzorzec jest wyszukiwany na początku łańcucha (czyli tak jakby zawsze na początku wzorca stał ^). Jeśli zostanie on znaleziony, | Wzorzec jest wyszukiwany na początku łańcucha (czyli tak jakby zawsze na początku wzorca stał <code>^</code>). Jeśli zostanie on znaleziony, wypisywana jest liczba dopasowanych znaków. W przeciwnym razie liczba dopasowanych znaków wynosi 0. | ||
Jeżeli we wzorcu były używane nawiasy \( i \), | Jeżeli we wzorcu były używane nawiasy <code>\(</code> i <code>\)</code>, zwracany jest łańcuch dopasowany w tym podwyrażeniu. | ||
Status wyjścia jest równy zero tylko wtedy, gdy do wzorca uda się dopasować niepusty ciąg znaków. | Status wyjścia jest równy zero tylko wtedy, gdy do wzorca uda się dopasować niepusty ciąg znaków. | ||
Oto kilka przykładów: | |||
* Wyświetlenie | * Wyświetlenie rozszerzenia pliku znajdującego się na zmiennej plik: | ||
expr "$plik" : ".*\.\([^.]*\)" | expr "$plik" : ".*\.\([^.]*\)" | ||
* Zmiana | * Zmiana rozszerzenia z <code>tar.gz</code> na <code>tgz</code> pliku na zmiennej plik: | ||
if expr "$plik" : ".*\.tar\.gz$"; then | if expr "$plik" : ".*\.tar\.gz$"; then | ||
mv $plik `expr "$plik" : "\(.*\.\)tar\.gz"`tgz | mv $plik `expr "$plik" : "\(.*\.\)tar\.gz"`tgz | ||
fi | fi | ||
: oczywiście if jest zbędny jeśli wiemy, że nazwa pliku na pewno kończy się na .tar.gz. | : oczywiście <code>if</code> jest zbędny jeśli wiemy, że nazwa pliku na pewno kończy się na <code>.tar.gz</code>. | ||
=== | === sed === | ||
Najprościej rzecz ujmując <code>sed</code> jest edytorem strumieniowym. Czyta ze standardowego wejścia lub z pliku, jeśli został podany jako argument, następnie wykonuje operacje podane w komendach edycyjnych i wynik wyrzuca zawsze na standardowe wyjście. | |||
Najczęściej <code>sed</code> jest używany z komendą edycyjną postaci <code>s/wzorzec/zamiennik/</code>, gdzie <code>wzorzec</code> jest podstawowym wyrażeniem regularnym, a <code>zamiennik</code> jest łańcuchem znakowym jakim będzie zastąpiony znaleziony wzorzec. W łańcuchu <code>zamiennik</code> możemy używać specjalnych sekwencji odnoszących się do znalezionego tekstu. <code>\</code>''n'', gdzie ''n'' jest liczbą, oznacza łańcuch skojarzony z ''n''-tą parą nawiasów <code>\( ... \)</code>. Znak <code>&</code> kojarzy z całym znalezionym łańcuchem. | |||
Na przykład, aby zamienić każdy ciąg wykrzykników w jeden wykrzyknik można użyć komendy <code>s/!\+/!/</code>: | |||
bashtest@host:~$ echo 'Hej!! Hej!!!! Tutaj!' | sed 's/!\+/!/' | |||
Hej! Hej!!!! Tutaj! | |||
bashtest@host:~$ | |||
Domyślnie <code>sed</code> wykonuje zastąpienie tylko przy pierwszym skojarzeniu wzorca w danej linii. Aby szukał wszystkich skojarzeń należy dodać przyrostek <code>g</code> do komendy edycyjnej: | |||
bashtest@host:~$ echo 'Hej!! Hej!!!! Tutaj!' | sed 's/!\+/!/g' | |||
Hej! Hej! Tutaj! | |||
bashtest@host:~$ | |||
Aby zamienić znak <code>.</code> na znak <code>,</code> we wszystkich liczbach zmiennopozycyjnych możemy użyć komendy | |||
s/\([0-9]\+\)\.\([0-9]\+\)/\1,\2/g | |||
Użyliśmy tutaj odnośników. <code>\1</code> oznacza grupę cyfr przed <code>.</code> w znalezionym łańcuchu, a <code>\2</code> oznacza grupę cyfr po <code>.</code>. | |||
Do rozdzielania komendy <code>s</code> nie trzeba wcale używać znaku <code>/</code>, ale może to być dowolny wybrany znak. Na przykład komenda | |||
<code>s+C:\\+/mnt/win/+g</code> | |||
zamieni wszystkie wystąpienia <code>C:\</code> na <code>/mnt/win/</code>. | |||
<code>sed</code> może też służyć do filtrowania wejścia. Opcja <code>-n</code> powoduje, że domyślnie nic nie jest wypisywane. Trzeba dodać przyrostek <code>p</code> do komendy, aby wynik został wypisany. Na przykład | |||
sed -n 's/[a-zA-Z0-9]/&/p' opis.txt | |||
wypisze tylko te linie pliku <code>opis.txt</code>, które zawierają znak alfanumeryczny. | |||
Ponadto <code>sed</code> posiada znacznie więcej różnych użytkowych funkcji. Można poprzedzić komendę adresem. Na przykład <code>sed '20s/.../.../'</code> zadziała tylko w 20 linii. Adresem może być numer linii, wyrażenie regularne albo zakres. Są też inne komendy, na przykład <code>sed '/^#/d'</code> usunie wszystkie linie, które zaczynają się od znaku <code>#</code>. | |||
=== | == Ćwiczenia == | ||
<ol> | |||
<li>Podaj wyrażenie regularne rozpoznające komentarz C: <code>/* ... */</code>. <p><div class="mw-collapsible mw-made=collapsible mw-collapsed">Rozwiązanie . <div class="mw-collapsible-content" style="display:none"><pre>/\*([^*]|(\*+[^*/]))*\*+/</pre></div></div></p></li> | |||
<li>Podaj wyrażenie regularne rozpoznające słowa, w których występuje przysta liczba wystąpień litery <code>a</code>. <p><div class="mw-collapsible mw-made=collapsible mw-collapsed">Rozwiązanie . <div class="mw-collapsible-content" style="display:none"><pre>[b-z]*(a[b-z]*a[b-z]*)*</pre></div></div></p></li> | |||
<li><sup>*</sup> Podaj wyrażenie regularne, które rozpoznaje ciąg znaków składający się z parzystej liczby wystąpień znaku <code>a</code> i znaku <code>b</code>. <div class="mw-collapsible mw-made=collapsible mw-collapsed"><p>Rozwiązanie .</p><div class="mw-collapsible-content" style="display:none"><p>Siłowe:<pre>(a(bb)*(a|ba(a(bb)*a)*(a(bb)*ba|b))|b(aa)*(b|ab(b(aa)*b)*(b(aa)*ab|a)))*</pre></p><p>Prostsze:<pre>(aa|bb|(ab|ba)(aa|bb)*(ab|ba))*</pre></p></div></div></li> | |||
</ol> |
Aktualna wersja na dzień 12:43, 30 wrz 2006
Po co są wyrażenia regulrane?
Częstym zadaniem, które wykonuje się podczas pracy z komputerem jest wyszukiwanie danego fragmentu tekstu w jakimś pliku, zmienianie go, sprawdzanie, czy dany tekst się w tym pliku znajduje. Generalnie potrzebujemy narzędzi do znajdywania wzorca o zadanej charakterystyce w jakimś większym tekście. Tą charakterystykę musimy jakoś podawać. W najprostszym przypadku podajemy po prostu tekst, jaki chcemy znaleźć. Na przykład, aby znaleźć wszystkie linie w pliku tekst.txt
, w których znajduje się fragment est
, wystarczy wykonać polecenie
grep est tekst.txt
Wtedy dla pliku tekst.txt
o zawartości
To jest plik tekstowy. Testujemy narzędzie grep. Trzecia linia. A to czwarta linia. To jest piąta linia.
w wyniku otrzymamy
To jest plik tekstowy. Testujemy narzędzie grep. To jest piąta linia.
Zamiast wyszukiwać daną frazę, możemy chcieć znaleźć bardziej skomplikowane wzorce, jak na przykład wszystkie adresy email, które znajdują się w danym tekście, czy też wszystkie formy słowa pies. W tych celach potrzebne są bardziej zaawansowane możliwości podawania charakterystyki frazy, której chcemy szukać. Taką możliwość dają właśnie wyrażenia regularne.
Składnia
Znaki
Najprostsze wyrażenia regularne składają się z ciągu znaków. Niektóre znaki są specjalne, więc aby rozpoznać któryś z nich, trzeba poprzedzać go backslashem. Następujące znaki są specjalne: .
^
$
*
?
[
]
\
. Na przykład, aby wyszukać w tekście frazę \abc[?]
używamy wyrażenia \\abc\[\?\]
.
Klasy znaków
Przypuśćmy, że chcemy znaleźć wystąpienie jednego z łańcuchów psa
, psu
i psy
. Zatem pierwsze dwa znaki są ustalone i są to ps
. Natomiast przy trzecim znaku mamy pewną dowolność. Chcemy, aby trzeci znak mógł być równy a
, u
lub y
. Do tego służą klasy znaków. Listę znaków, którą chcemy rozpoznać, umieszczamy pomiędzy nawiasami kwadratowymi: [auy]
. Nasze wyrażenie wygląda więc tak: ps[auy]
.
bashtest@host:~$ grep ps[auy] <<KONIEC > psami > psom > psu > ps > KONIEC psami psu bashtest@host:~$
W klasach możemy podawać przedziały znaków używając myślnika. Na przykład [0-9]
rozpoznaje dowolną cyfrę. Możemy podawać kilka przedziałów: [a-zA-Z]
rozpozna dowolną literę angielską.
Jeśli umieścimy znak ^
na początku opisu klasy, będzie to oznaczać rozpoznawanie wszystkich znaków nie znajdujących się na liście. Na przykład [^xX]
oznacza dowolny znak różny od x
i X
. Oczywiście możemy podawać też przedziały.
Jeśli chcemy umieścić na liście znak ]
, należy go podać jako pierwszy na liście. Aby umieścić ^
, należy go umieścić w dowolnym miejscu, byle nie na początku. Wreszcie, aby umieścić -
, należy go umieścić na końcu listy.
Ponadto mamy możliwość kojarzenia dowolnego znaku oprócz znaku końca linii. Do tego służy znak specjalny .
(kropka).
Powtórzenia
Znak specjalny *
próbuje dopasować poprzedzający go element zero lub więcej razy w szukanym tekście. Na przykład =*
dopasowuje się do ciągu znaków równości (być może ciągu pustego).
Inny przykład, wyrażenie [a-zA-Z][a-zA-Z0-9_]*
reprezentuje identyfikator, tzn. niepusty łańcuch znaków zaczynający się od litery i składający się z liter, cyfr oraz znaków podkreślenia.
Znak specjalny +
jest podobny do *
z tym, że próbuje on dopasować poprzedzający go element jeden lub więcej razy.
Znak specjalny ?
mówi, że poprzedzający element może wystąpić, bądź też nie. Na przykład wyrażenie -?[0-9]+
oznacza dowolny niepusty ciąg cyfr, być może poprzedzony znakiem -
. Czyli takie wyrażenie może służyć do rozpoznawania dowolnej liczby całkowitej (jeżeli dopuścimy to, że zapis dziesiętny liczby może mieć wiodące zera).
Ponadto ilość powtórzeń można podać bardziej w uniwersalny sposób:
symbol powtórzenia | liczba powtórzeń poprzedzającego elementu |
---|---|
{n} |
dokładnie n
|
{n,} |
co najmniej n
|
{,m} |
co najwyżej m
|
{n,m} |
co najmniej n i co najwyżej m
|
Pozycjonowanie
Znaczniki pozycjonujące są takimi symbolami, które nie są kojarzone z żadnym ciągiem znaków, ale z pozycją w tekście spełniającą pewne ustalone warunki.
Jeśli chcemy, aby wzorzec był dopasowywany od początku linii, wyrażenie zaczynamy znakiem ^
. Jeśli chcemy, aby wzorzec był dopasowywany do końca linii, wyrażenie kończymy znakiem $
. Gdy użyjemy oba znaki, wzorzec będzie dopasowywany do całej linii.
Na przykład, żeby znaleźć linie, w których znajduje się dokładnie jedna kropka, możemy użyć wyrażenia ^[^.]*\.[^.]*$
. Użycie ^
na początku i $
na końcu oznacza, że zawsze będzie próba dopasowania całej linii. Następnie [^.]*
oznacza dowolny ciąg znaków nie zawierający kropki, a \.
oznacza wystąpienie kropki (trzeba użyć \
, bo .
jest znakiem specjalnym).
Aby znaleźć linię, która zaczyna się od dowolnej liczby spacji, liczby, później kropki, a następnie spacji i dużej litery, używamy wyrażenia ^ *[0-9][0-9]*\. *[A-Z]
. Za pomocą tego wyrażenia zostaną znalezione na przykład
1. Wstęp 2.Wprowadzenie 13. Zakończenie
a nie zostaną znalezione takie linie
1 Trafienie 3. echo - komenda wbudowana 4 . Punkt czwarty 2006 ^ 1. Wstęp
Oprócz znaczników rozpoznających początek i koniec linii, mamy też szereg znaczników rozpoznających końce słowa:
\< |
znacznik mówiący, że w tym miejscu zaczyna się nowe słowo |
\> |
znacznik mówiący, że w tym miejscu kończy się słowo |
\b |
znacznik mówiący, że w tym miejscu jest krawędź słowa (nie zaczyna ani nie kończy się żadne słowo) |
\B |
znacznik mówiący, że w tym miejscu nie znajduje się krawędź słowa |
Dokładne definicje, co to jest brzeg słowa, są nieprzyjemnie skomplikowane i je pominiemy. Przyjrzyjmy się przykładowi. Dla frazy Jola jest lojalna
następujące wyrażenia zostaną dopasowane do wycinka tej frazy:
\<jest\>
\bjest\b
\Best\b
\Bes\B
a poniższe wyrażenia nie zostaną dopasowane:
\<est\>
\best\b
\Bes\b
\>jest\<
Alternatywa
Jeśli chcemy, aby był rozpoznawany jeden z dwóch możliwych napisów, to możemy użyć alternatywy |
, na przykład jeden|dwa
rozpozna albo słowo jeden
albo słowo dwa
. Operator |
może mieć jako argumenty też inne wyrażenia, na przykład [0-9]*|[a-z]*|[A-Z]*
rozpoznaje albo ciąg cyfr, albo ciąg małych liter, albo ciąg dużych liter.
Priorytety i nawiasowanie
Największy priorytet mają operatory powtórzenia. Jeśli chcemy mieć wyrażenie oznaczające powtórzenie większej liczby elementów, to możemy zrobić to poprzez stosowanie nawiasów ( ... )
. Na przykład wyrażenie ([a-z][a-z])*
oznacza ciąg składający się z parzystej liczby małych literek.
Najmniejszy priorytet ma alternatywa. Jeśli chcemy wpisać wyrażenie, które rozpoznaje wszystkie słowa, które rozpoczynają się od Ta
lub od Do
, to nie możemy napisać Ta|Do[a-z]*
, gdyż takie wyrażenie rozpoznaje albo słowo Ta
, albo ciąg literek zaczynający się od Do
. Z pomocą przychodzą nawiasy: (Ta|Do)[a-z]*
.
Wyrażenia podstawowe i rozszerzone
Wyrażenia regularne dzielimy na podstawowe i rozszerzone. W wyrażeniach podstawowych znaki ?
, +
, {
, |
, (
, )
nie mają specjalnego znaczenia, zamiast nich trzeba używać wersji z backslashem: \?
, \+
, \{
, \|
, \(
, \)
. W wyrażeniach rozszerzonych możemy stosować wszystkie wymienione znaki specjalne.
Rozróżnienie takie powstało na skutek różnych narzędzi i zachowania kompatybilności między nimi. Niektóre narzędzia używają składni z podstawowymi, a niektóre z rozszerzonymi wersjami wyrażeń regularnych. Przy omawianiu poszczególnych komend powiemy, które używają jakiej składni.
Przykłady wykorzystania
grep
Polecenie grep
jest podstawowym narzędziem do wyszukiwania wzorca w tekście z użyciem wyrażeń regularnych. grep
używa podstawowych wyrażeń. Jeśli jest potrzeba użycia rozszerzonych wyrażeń można użyć wtedy w zastępstwie polecenia egrep
.
Podstawowe użycie, to
grep wzorzec
gdzie wzorzec jest wyrażeniem regularnym. Wzorzec jest wyszukiwany w standardowym wejściu i na standardowe wyjście są wypisywane linie zawierające wzorzec.
Można podać plik lub pliki jako argumenty:
grep wzorzec plik
lub
grep wzorzec plik1 plik2 ...
W przypadku jednego pliku, działanie jest takie samo jak bez argumentów, z tą różnicą, że dane są czytane z pliku, a nie ze standardowego wejścia.
W wersji z wieloma plikami wzorzec wyszukiwany jest w każdym pliku. Wyświetlane są linie zawierające wystąpienie wzorca poprzedzone nazwą pliku, w którym został on znaleziony.
Ponadto jest wiele opcji kontrolujących sposób szukania i wyświetlane informacje. Omówimy najprzydatniejsze z nich.
-e wzorzec
- alternatywny sposób podania wzorca; przydatny, gdy wyrażenie regularne może zaczynać się od znaku
-
-i
- ignoruje rozróżnianie wielkich liter,
-c
- zlicza tylko liczbę wystąpień wzorca,
-w
- dopasowuje wzorzec tylko do całych słów,
-x
- dopasowuje wzorzec tylko do całych linii,
-v
- odwraca sens dopasowania i wyszukuje tylko linie w których nie udało się dopasować wzorca,
-q
- nic nie wypisuje na standardowe wyjście i kończy działania na pierwszym dopasowaniu; przydatne, gdy polecenie
grep
chcemy użyć jako warunku.
grep
zwraca zero jako kod wyjścia, gdy wzorzec uda się znaleźć i wartość niezerową w przeciwnym przypadku. Można to wykorzystać w połączeniu z opcją -q
w instrukcjach warunkowych. Na przykład
if grep -qw TODO opis_prac.txt; then echo "Zostało jeszcze coś do zrobienia" fi
Jeśli chcemy z pliku usunąć linie, które pasują do wzorca możemy zrobić to w następujący sposób:
TMPFILE=/tmp/xyzabcd cp plik $TMPFILE grep -xv wzorzec $TMPFILE >plik rm -f $TMPFILE
expr
Polecenie expr
oprócz obliczania wyrażeń arytmetycznych ma też podstawowe operacje na łańcuchach znakowych. Szczególnie przydatny jest operator :
. Jego składnia to
expr łańcuch : wzorzec
Wzorzec jest wyszukiwany na początku łańcucha (czyli tak jakby zawsze na początku wzorca stał ^
). Jeśli zostanie on znaleziony, wypisywana jest liczba dopasowanych znaków. W przeciwnym razie liczba dopasowanych znaków wynosi 0.
Jeżeli we wzorcu były używane nawiasy \(
i \)
, zwracany jest łańcuch dopasowany w tym podwyrażeniu.
Status wyjścia jest równy zero tylko wtedy, gdy do wzorca uda się dopasować niepusty ciąg znaków.
Oto kilka przykładów:
- Wyświetlenie rozszerzenia pliku znajdującego się na zmiennej plik:
expr "$plik" : ".*\.\([^.]*\)"
- Zmiana rozszerzenia z
tar.gz
natgz
pliku na zmiennej plik:
if expr "$plik" : ".*\.tar\.gz$"; then mv $plik `expr "$plik" : "\(.*\.\)tar\.gz"`tgz fi
- oczywiście
if
jest zbędny jeśli wiemy, że nazwa pliku na pewno kończy się na.tar.gz
.
sed
Najprościej rzecz ujmując sed
jest edytorem strumieniowym. Czyta ze standardowego wejścia lub z pliku, jeśli został podany jako argument, następnie wykonuje operacje podane w komendach edycyjnych i wynik wyrzuca zawsze na standardowe wyjście.
Najczęściej sed
jest używany z komendą edycyjną postaci s/wzorzec/zamiennik/
, gdzie wzorzec
jest podstawowym wyrażeniem regularnym, a zamiennik
jest łańcuchem znakowym jakim będzie zastąpiony znaleziony wzorzec. W łańcuchu zamiennik
możemy używać specjalnych sekwencji odnoszących się do znalezionego tekstu. \
n, gdzie n jest liczbą, oznacza łańcuch skojarzony z n-tą parą nawiasów \( ... \)
. Znak &
kojarzy z całym znalezionym łańcuchem.
Na przykład, aby zamienić każdy ciąg wykrzykników w jeden wykrzyknik można użyć komendy s/!\+/!/
:
bashtest@host:~$ echo 'Hej!! Hej!!!! Tutaj!' | sed 's/!\+/!/' Hej! Hej!!!! Tutaj! bashtest@host:~$
Domyślnie sed
wykonuje zastąpienie tylko przy pierwszym skojarzeniu wzorca w danej linii. Aby szukał wszystkich skojarzeń należy dodać przyrostek g
do komendy edycyjnej:
bashtest@host:~$ echo 'Hej!! Hej!!!! Tutaj!' | sed 's/!\+/!/g' Hej! Hej! Tutaj! bashtest@host:~$
Aby zamienić znak .
na znak ,
we wszystkich liczbach zmiennopozycyjnych możemy użyć komendy
s/\([0-9]\+\)\.\([0-9]\+\)/\1,\2/g
Użyliśmy tutaj odnośników. \1
oznacza grupę cyfr przed .
w znalezionym łańcuchu, a \2
oznacza grupę cyfr po .
.
Do rozdzielania komendy s
nie trzeba wcale używać znaku /
, ale może to być dowolny wybrany znak. Na przykład komenda
s+C:\\+/mnt/win/+g
zamieni wszystkie wystąpienia C:\
na /mnt/win/
.
sed
może też służyć do filtrowania wejścia. Opcja -n
powoduje, że domyślnie nic nie jest wypisywane. Trzeba dodać przyrostek p
do komendy, aby wynik został wypisany. Na przykład
sed -n 's/[a-zA-Z0-9]/&/p' opis.txt
wypisze tylko te linie pliku opis.txt
, które zawierają znak alfanumeryczny.
Ponadto sed
posiada znacznie więcej różnych użytkowych funkcji. Można poprzedzić komendę adresem. Na przykład sed '20s/.../.../'
zadziała tylko w 20 linii. Adresem może być numer linii, wyrażenie regularne albo zakres. Są też inne komendy, na przykład sed '/^#/d'
usunie wszystkie linie, które zaczynają się od znaku #
.
Ćwiczenia
- Podaj wyrażenie regularne rozpoznające komentarz C:
/* ... */
.Rozwiązanie . - Podaj wyrażenie regularne rozpoznające słowa, w których występuje przysta liczba wystąpień litery
a
.Rozwiązanie . - * Podaj wyrażenie regularne, które rozpoznaje ciąg znaków składający się z parzystej liczby wystąpień znaku
a
i znakub
.Rozwiązanie .