sobota, 26 czerwca 2010

Wyszukiwanie, wyrażenia regularne

Systemy uniksowe oferują kilka przydatnych poleceń służących do wyszukiwania plików, ciągów tekstu, które w połączeniu z przedstawionymi powyżej strumieniami i przekierowaniami oraz z wyrażeniami regularnymi stanowią bardzo potężne narzędzie administratorskie.

1. Wyrażenia regularne

Wyrażenia regularne są wzorcami opisującymi, zastępującymi łańcuchy tekstów, zostały one wprowadzone od samego początku istnienia Uniksa przez jednego z jego twórców — Kena Thompsona. Wyrażenia regularne są bardzo efektywnym sposobem pracy z tekstem i zdecydowanie warto je opanować.

Symbol Zastępuje
. dowolny znak
^ dopasuj występujące po operatorze wyrażenie do początku wiersza
$ dopasuj poprzedzające wyrażenie do końca wiersza
\x znaki specjalne, gdzie x to znak specjalny np. \$ zastąpi znak dolara
[lista] zastępuje dowolny znak spośród tych wymienionych na liście, mogą to być przedziały np. [0-9] lub [a-d]
() grupowanie wyrażeń regularnych
? dokładnie jeden element wcześniejszy
a|b dopasuje wyrażenie a lub wyrażenie b
* dopasuj zero lub więcej wyrażeń znaku poprzedzający operator
+ jeden lub więcej elementów poprzedzających operator

2. Wyrażenia regularne a znaki globalne

Warto nadmienić że, bash do wersji 3.0 nie miał wbudowanej obsługi wyrażeń regularnych, które to były wykorzystywane przez programy pracujące na strumieniach tekstu np.: sed, awk czy też grep. Za to wbudowana była obsług wyrażeń globalnych i znaków wieloznacznych (ang. wildcards).

Symbol Zastępuje
* dowolny ciąg znaków
? dokładnie jeden znak
[lista] zastępuje dowolny znak spośród tych wymienionych na liście, mogą to być przedziały np. [0-9] lub [a-d]
[^lista] wybrane zostaną znaki, które nie są na liście
{} grupuje wyrażenie globalne

Wyrażenia regularne pozwalają wyszukać dany łańcuch w strumieniu tekstu, podczas gdy wyrażenia globalne zastępują fragmenty tekstu. W poniższym, bardzo prostym przykładzie usunięto wszystkie plik rozpoczynające się na literę “a”.

adam@laptop:~/Dokumenty/jakilinux.org/przykłady$ ls
aa abc nowy.txt przykład.txt
ab error.txt polecenie.txt wszystko_o_konsoli.txt
adam@laptop:~/Dokumenty/jakilinux.org/przykłady$ rm a*
adam@laptop:~/Dokumenty/jakilinux.org/przykłady$ ls
error.txt nowy.txt polecenie.txt przykład.txt wszystko_o_konsoli.txt

W tym natomiast usunięto wszystkie które nie mają jako pierwszej litery z zakresu b do z i dalsza ich nazwa to dowolny ciąg znaków.

adam@laptop:~/Dokumenty/jakilinux.org/przykłady$ ls
aa abc nowy.txt przykład.txt
ab error.txt polecenie.txt wszystko_o_konsoli.txt
adam@laptop:~/Dokumenty/jakilinux.org/przykłady$ rm [^b-z]*
adam@laptop:~/Dokumenty/jakilinux.org/przykłady$ ls
error.txt nowy.txt polecenie.txt przykład.txt wszystko_o_konsoli.txt

3. grep

grep to powszechnie wykorzystywany program do wyszukiwania w strumieniu wejścia ciągów tekstowych, pasujących do podanego wyrażenia regularnego. Występuje on w każdym systemie uniksowym a jego autorem jest Ken Thompson.
grep ma kilka przydatnych parametrów:

  • -c — wyświetla tylko liczbę znalezionych linii,
  • -n — wyświetlany jest numer linii w pliku, w którym znaleziono dany ciąg znaków,
  • -w — wyszukuje tylko całe słowa,
  • -x — wyszukuje tylko całe linie.
adam@laptop:~/Dokumenty/jakilinux.org/przykłady$ dmesg | grep Mouse
[ 15.436000] input: USB-PS/2 Optical Mouse as /class/input/input2
[ 15.436000] input: USB HID v1.10 Mouse [USB-PS/2 Optical Mouse]
on usb-0000:00:1d.0-2

W powyższym przykładzie wyjście polecenia dmesg (wyświetlającego informacje dziennika zdarzeń jądra systemu) przekierowałem za pomocą potoku (operator |) do polecenia grep, gdzie jako wyrażenia regularnego użyłem słowa “Mouse” (wielkość liter ma znaczenie). Polecenie przefiltrowało wejście i wyświetliło jedynie te linie, które zawierają słowo “Mouse”.

Do kolejnych przykładów załóżmy, że mamy plik tekstowy, wyrażenia.txt o zawartości:

1. owoc
2. rower
3. dom
4. auto
5. płyta
ananas

Tak więc:

grep ^[1-6]..a wyrażenia.txt
4. auto
ananas

Powyższe wyrażenie wyszuka wszystkie łańcuchy, które zaczynają się od liczb z przedziału od 1 do 6, dalej zawierają dwa dowolne znaki (w naszym przypadku kropkę i spację), dalej zawierają literę “a”, za nią dowolny już ciąg znaków.
Wyrażenie poniżej wyświetli każdą linię kończącą się na literę “a”.

grep a$ wyrażenia.txt
5. płyta

grep a.*a  wyrażenia.txt
ananas

Powyższe wyrażenie wyszuka dowolny ciąg zaczynający się na a, dalej zawierający dowolny ciąg znaków, i kończący się na a. W tym przypadku warto zwrócić uwagę na zapis “.*”. Jak pamiętamy, znak * w wyrażeniach regularnych zwróci zero lub więcej znaków poprzedzających ten operator, natomiast operator . oznacza dowolny znak. Ujmując to prościej, wyszukujemy w ten sposób zero lub więcej wystąpień dowolnego znaku.

grep "\(1\|4\)" wyrażenia.txt
1. owoc
4. auto

Przedstawione powyżej wyrażenie, jest i tyle ciekawe, że użyto w nim znaków, które bash traktuje jako znaki specjalne. Aby to ominąć, postawiłem przed nimi znak \, a całość zamknąłem w cudzysłów, aby uniknąć interpretowania przez bash tego wyrażenia (zrobi to grep).
Tematyka wyrażeń regularnych jest bardzo rozbudowana, dlatego zachęcam do samodzielnego eksperymentowania i ćwiczeń.

4. find

Polecenie find przeszukuje drzewo katalogów w poszukiwaniu plików lub katalogów o podanej nazwie lub jej części, lub o podanych kryteriach takich jak: rozmiar, typ, właściciel plików, data utworzenia lub data ostatniej modyfikacji. Najprostsze wywołanie programu find może wyglądać następująco:

adam@laptop:~$ find . -name linux
./Downloads/Firefox/vmware-server-distrib/lib/perl5/\\
site_perl/5.005/i386-linux/linux
./Downloads/Firefox/tp_smapi-0.31/include/linux

Składnia tego polecenia jest następująca: find katalogi_startowe kryterium wyszukiwania i operacje, które należy wykonać na wyszukanych elementach. W powyższym przykładzie katalogiem startowym jest katalog bieżący, jak już wcześniej wspominałem każdy katalog zawiera dowiązanie do siebie samego reprezentowane przez kropkę, oraz kryterium wyszukiwania — wszystkie pliki i katalogi zwierające frazę “linux”. Wyszukiwanie rozpoczyna się od katalogu startowego i postępuje w dół drzewa katalogów, tzn. najpierw katalog bieżący, a potem jego podkatalogi, oczywiście o ile ma się prawo do ich odczytu. W programie find można (i warto a wręcz należy) używać znaków globalnych przedstawionych wcześniej.
find umożliwia wyszukiwanie według typu.

adam@laptop:~$ find . -type d

Powyższy przykład wyszuka wszystkie katalogu znajdujące się w bieżącej lokalizacji. Kryteria oczywiście można łączyć.

adam@laptop:~$ find . -type d -name Dokumenty
./Dokumenty
./Dokumenty/jakilinux.org/Dokumenty

Powyższe wywołanie znajdzie wszystkie katalogi w bieżącej lokalizacji zawierające w swej nazwie zwrot “Dokumenty”. Możliwe typy plików przedstawia poniższa tabela.

Parametr Rodzaj pliku
b urządzenie blokowe
c urządzenie znakowe
d katalog
f zwykły plik
l dowiązanie symboliczne
s gniazdo (ang. socket)

find wywołany z parametrem -size wartość, wyszuka pliki o wielkości podanej w parametrze wartość, jeżeli do wartości dodamy znak + (np. -size +wartość), program wyszuka pliki większe od podanej wartości. Jeżeli dodamy znak - (np. -size -wartość), wyszukane zostaną pliki mniejsze od podanej wartości. Domyślna wartość to 512 bitowe bloki, pozostałe wartości to:

  • c — bajty,
  • k — kilobajty,
  • M — megabajty,
  • G — gigabajty.

W poniższym przykładzie zostaną wyszukane wszystkie pliki o rozmiarze większym niż 100 MB ale mniejszym niż 200 MB.

adam@laptop:~$ find . -size +100M -size -200M
./plan9_07010zip

Pozostałe kryteria wyszukiwania przedstawia poniższa tabela.

-atime n Ostatni dostęp miał miejsce n dni temu
-mtime n Plik został zmodyfikowany n dni temu
-newer plik Wyszukiwany plik został zmodyfikowany wcześniej niż podany plik
-links n Plik zawiera dokładnie n twardych dowiązań
-perm p Plik ma uprawnienia, gdzie p to liczbowy tryb dostępu
-user użytkownik Właścicielem pliku jest użytkownik
-group grupa Właścicielem pliku jest grupa
-empty Puste pliki

Opcje liczbowe można poprzedzać znakami + i -, które oznaczają odpowiednio “więcej niż” oraz “mniej niż”, podobnie jak miało to miejsce w kryterium time opisanym wcześniej.

Jak już wcześniej wspominałem, polecenie find może wykonać określone operacje na plikach, które znajdzie. Domyślną operacją jest -print, która wypisuje nazwy łacznie z adresami plików. W niektórych powłokach należy dodawać tę opcję za każdym razem.

Kolejna możliwa akcja to -ls, która wypisuje informacje o plikach w ten sam sposób, co polecenie ls uruchomione z parametrami -lids. Ostatnia możliwość to uruchomienie z parametrem -exec i wykonanie dowolnego polecenia na znalezionych plikach.

adam@laptop:~$ find . -size +100M -size -150M -ls
2485508 115744 -rw-r--r-- 1 adam adam 118398976 maj 2 22:44
./plan9/plan9_compressed.img
find . -size +110M -size -150M -exec cp {} /home/adam/pliki/ \;

Pierwszy z powyższych przykładów, jak widać, wypisze szczegółowe dane wyszukanych plików. Drugi jest ciekawszy i wymaga dokładniejszej analizy. find wykona na plikach operację podaną wraz z parametrem. W tym przypadku, wszystkie pliki większe niż 110 MB i mniejsze niż 150 MB zostaną skopiowane do katalogu pliki, podwójny nawias klamrowy {} oznacza, że operacja ma być wykonana na każdym pliku a \ przed ; chroni przed błędną interpretacją polecenia przez powłokę.
find ma jeszcze inne ciekawe opcje: -ok — działa podobnie jak -exec z tą różnicą, że przed każdą operacją użytkownik proszony jest o potwierdzenie działania, -prune powoduje, że find nie wchodzi do żadnego z napotkanych katalogów.

Składnia find umożliwia tworzenie złożonych wyrażeń, łączenia kryteriów. Domyślnie kryteria łączone są za pomocą logicznej koniunkcji (AND): (-a). Wszystkie kryteria muszą być spełnione, aby plik został uznany za zgodny z kryteriami. Operator łączenia alternatywy logicznej (OR): -o, natomiast operator negacji to: \! Istnieje możliwość wykorzystania nawiasów w celu grupowania kryteriów \( \).
Ważnym parametrem polecenia find jest parametr -print0, w przypadku jego użycia, nazwy znalezionych plików nie są rozdzielane znakiem nowej linii a znakiem null. Rozpatrzmy przykład poniżej, w którym użyjemy dwóch plików: raport czerwiec.txt i raportczerwiec.txt.

adam@laptop:~$ find . -name "raport*" | xargs rm
rm: nie można usunąć `./raport': No such file or directory
rm: nie można usunąć `czerwiec.txt': No such file or directory

Poleceniu rm nie udało sie usunąć pliku raport czerwiec.txt, ponieważ zawiera on spację w nazwie i jego nazwa została w tym miejscu rozdzielona.

find . -name "raport*" -print0 | xargs -0 rm

Polecenie print otrzymało opcję -print0, natomiast polecenie xargs opcję -0, dzięki czemu bez problemu usunięty został plik zawierający spację. Uwaga! Opcja -print0 dostępna jest tylko w wersji GNU polecenia find.

Brak komentarzy:

Prześlij komentarz