Wyrażenia regularne

Poznamy teraz kilka narzędzi ułatwiających codzienną pracę z plikami zawierającymi teksty. Przydatne są one zwłaszcza wtedy, gdy pracujemy z większą liczbą plików.

Odszukiwanie wiersza

Często zdarza nam się poszukiwać pliku, zawierającego pewien charakterystyczny fragment tekstu. Służy do tego rodzina poleceń fgrep, grep i egrep. Pozwalają one wyszukiwać pliki, zawierające wiersze o wskazanej postaci. Pierwszym argumentem polecenia fgrep powinien być napis, którego szukamy. W najprostszym przypadku może to być pojedyncze słowo. Pozostałe argumenty opisują pliki do przeszukania.

$ ls
postacie
przybysze
wszyscy
zwrotka
$ cat zwrotka
Hejże ha! Niech Kubuś żyje!
Niechaj tyje, je i pije!
Czy przy środzie, czy przy wtorku,
On w miodowym jest humorku.
I niewiele o co dba,
Gdy na nosie miodek ma!

Więc śpiewajcie wszyscy dzisiaj
Hymn na cześć Puchatka-Misia
Który swe Conieco zje
Za godzinę lub za dwie!
$ fgrep Kubuś zwrotka
Hejże ha! Niech Kubuś żyje!
$ fgrep Kubuś *
postacie: Kubuś Puchatek
wszyscy: Kubuś Puchatek
zwrotka: Hejże ha! Niech Kubuś żyje!
$ _

Polecenie fgrep wypisuje wszystkie linie zawierające poszukiwany napis. Jeśli przeszukiwaliśmy kilka plików to każda linia poprzedzana jest nazwą pliku, z którego pochodzi. Może się zdarzyć, że poszukiwany tekst zawiera znak służący do rozdzielania argumentów polecenia. Takim znakiem jest na przykład spacja. Musimy wtedy otoczyć tekst cudzysłowami.

$ fgrep "Kubuś Puchatek" *
postacie: Kubuś Puchatek
wszyscy: Kubuś Puchatek
$ _

Przy bardziej skomplikowanym przeszukiwaniu polecenie fgrep przestaje wystarczać. Musimy posłużyć się poleceniami grep lub egrep, używającymi wyrażeń regularnych. Warto zapoznać się z wyrażeniami regularnymi, ponieważ oprócz występowania w argumentach poleceń wyszukiwania wykorzystywane są one w edytorach.

Proste wyrażenia regularne

Wyrażenia regularne przypominają budową wzorce nazw plików, różnią się jednak od nich przeznaczeniem. Zadaniem ich jest opisywanie dowolnych napisów. Budowane są na podobnej zasadzie, ale inny nieco jest repertuar metaznaków i spełniane przez nie funkcje. Metaznakiem pasującym do dowolnego pojedynczego znaku jest kropka.

$ grep "Niech." zwrotka
Hejże ha! Niech Kubuś żyje!
Niechaj tyje, je i pije!
$ _

Odszukane zostały linie, zawierające pasujące napisy ,,Niech '' oraz ,,Niecha''. Należy pamiętać, aby wyrażenia regularne otaczać cudzysłowami. W tym przypadku nie miało to akurat znaczenia, ale inne metaznaki mogą zostać przetworzone przez interpreter poleceń.

Aby ułatwić sobie orientację w wyszukiwanych liniach warto skorzystać z opcji -n, dzięki której wypisywane linie poprzedzane są numerem podającym ich pozycję w pliku.

$ grep -n "z. " zwrotka 
3: Czy przy środzie, czy przy wtorku,
11: Za godzinę lub za dwie!
$ grep

Inna pożyteczna opcja to -v, odwracająca działanie polecenia grep. Po jej otrzymaniu polecenie grep wyszuka wszystkie linie nie pasujące do podanego wyrażenia, na przykład nie zawierające podanego napisu. Poszukajmy linii nie zawierających wykrzyknika.

$ grep -v "!" zwrotka
Czy przy środzie, czy przy wtorku,
On w miodowym jest humorku.
I niewiele o co dba,

Więc śpiewajcie wszyscy dzisiaj
Hymn na cześć Puchatka-Misia
Który swe Conieco zje
$ _

Spróbujmy teraz odnaleźć wszystkie linie, w których występują czteroliterowe słowa rozpoczynające się literą p.

$ grep -n "p..." zwrotka
2: Niechaj tyje, je i pije!
3: Czy przy środzie, czy przy wtorku,
8: Więc śpiewajcie wszyscy dzisiaj
$ _

Wynik jest częściowo poprawny. Otrzymaliśmy obie poszukiwane linie oraz jedną zbędną. Zmodyfikujmy nieco nasz wzorzec, aby wykluczyć zbyt długie słowa.

$ grep -n " p... " zwrotka
3: Czy przy środzie, czy przy wtorku,
$ _

Tym razem wykluczyliśmy za dużo. Brakującą linię można by odszukać wywołując drugi raz polecenie grep z argumentem ,,p....!''. Lepiej jednak wykorzystać klasy znaków.

Klasy znaków

Skorzystamy teraz z nawiasów kwadratowych, które w wyrażeniach regularnych podobnie jak we wzorcach nazw plików służą do zapisywania klas znaków.

$ grep -n " p...[ !]" zwrotka
2: Niechaj tyje, je i pije!
3: Czy przy środzie, czy przy wtorku,
$ _

Klasy często stosuje się do utożsamiania dużych i małych liter przy wyszukiwaniu.

$ grep "[Nn]ie" zwrotka1
Hejże ha! Niech Kubuś żyje!
Niechaj tyje, je i pije!
I niewiele o co dba,
Który swe Conieco zje
$ _

Ten sam efekt można było osiągnąć używając opcji -i, wymuszającej ignorowanie pocztu liter.

$ grep -i "nie" zwrotka1
Hejże ha! Niech Kubuś żyje!
Niechaj tyje, je i pije!
I niewiele o co dba,
Który swe Conieco zje
$ _

Przypomnijmy sobie jeszcze rolę myślnika przy zapisywaniu klas.

$ grep "[a-z]nie" zwrotka
Który swe Conieco zje
$ _

W wyrażeniach regularnych oprócz wybierania znaków z pewnej klasy możemy zażądać unikania znaków z pewnej klasy. Jeśli pierwszym znakiem (pomijając początkowy nawias) w zapisie klasy jest ^, to klasę taką nazywamy zanegowaną. Klasa zanegowana pasuje do dowolnych znaków nie występujących w klasie.

$ grep "[^Nn]ie" zwrotka
I niewiele o co dba,
Gdy na nosie miodek ma!
Więc śpiewajcie wszyscy dzisiaj
$ _

Klasa zanegowana przydaje się do lepszego zapisania granic słowa.

$ grep "[^a-zA-Z]p...[^a-zA-Z]" zwrotka
2: Niechaj tyje, je i pije!
3: Czy przy środzie, czy przy wtorku,
$ _

Metaznaki pomocnicze

Metaznaki ^ i $ umożliwiają ograniczenie wyszukiwania do napisów znajdujących się na początku względnie końcu linii. Jeśli ^ jest pierwszym znakiem wyrażenia regularnego, to szukany tekst musi znajdować się na początku linii.

$ grep "^Niech" zwrotka
Niechaj tyje, je i pije!
$ _

Pierwsza linia nie została odszukana, gdyż napis ,,Niech'' występuje w niej w środku linii. Metaznak $ spełnia analogiczną rolę pod warunkiem, że występuje na końcu wyrażenia. Tekst zostanie odszukany tylko wtedy, gdy wystąpi na końcu linii.

$ grep "je$" zwrotka
Który swe Conieco zje
$ _

Używając poznanych przed chwilą metaznaków można wyszukać w tekście puste linie.

$ grep -n "^$" zwrotka
7:
$ _

Czasami szukany fragment tekstu zawiera znaki specjalne. Tak będzie przy szukaniu linii kończących się kropką. Podanie wzorca .$ spowodowałoby wypisanie wszystkich niepustych linii. Aby zapobiec specjalnemu potraktowaniu znaku należy go poprzedzić odwrotnym ukośnikiem (\). Służy on więc do przesłaniania następującego po nim metaznaku.

$ grep "\.$" zwrotka
On w miodowym jest humorku.
$ _

Domknięcia

Pokazaliśmy powyżej, jak odszukać puste linie. Czasem jednak taka linia może zawierać znaki niewidoczne -- najczęściej spacje lub znaki tabulacji. Stosując podane poprzednio wyrażenie nie zostałyby one odszukane. Jak jednak powiedzieć: ,,kilka spacji''?

Wyrażenie służące do opisu ciągu jednakowych znaków nazywamy domknięciem. Składa się ono z dowolnego elementu, po którym stoi metaznak *, na przykład a* oznacza ciąg liter 'a' dowolnej długości (może to być pusty ciąg, co wykorzystamy za chwilę).

Teraz już możemy odszukać linie zawierające wyłącznie znaki niewidoczne. Uwaga: wewnątrz nawiasów kwadratowych znajdują się znaki spacji i tabulacji.

$ grep -n "^[     ]*$" zwrotka
7:
$ _

Powyższe wyszukiwanie obejmuje również całkowicie puste linie, dlatego efekt jest ten sam co poprzednio. Zwróćmy uwagę na inną funkcję metaznaku * w wyrażeniach regularnych niż we wzorcach nazw plików.

Na koniec spróbujmy odszukać linie, zaczynające się i kończące literą.

$ grep "^[A-Za-z].*[A-Za-z]$" zwrotka
Więc śpiewajcie wszyscy dzisiaj
Który swe Conieco zje
$ _