pdf - Środowisko programisty

Transkrypt

pdf - Środowisko programisty
Środowisko programisty
Zestaw 4
Semestr zimowy 2016/2017
Kraków, 3,8,9 listopada 2016
sed 2, pcre
W Sedzie występują trzy komendy: i, a, c, które pozwalają na dodawanie nowych linii
do strumenia wyjściowego. Poniewać dodawana jest cała linia, zostaje ona zakończona
znakiem nowej linii. W poniższym przykładzie każda linia ze wzorcem WORD zostanie
poprzedzona odpowiednią linią (komenda i), zastąpiona przy użyciu komendy c. Komenda
a wstawi odpowiednia linią po linii ze wzorcem WORD.
#!/bin/bash
sed ’
/WORD/ {
i\Add this line before
a\Add this line after
c\Change the line to this one
}’
Kolejny przykład dodaje trzy linie po linii zawierającej wzorzec WORD.
#!/bin/bash
sed ’
/WORD/ a\
Add this line\
This line\
And this line’
Komenda = drukuje numer bieżącej linii:
sed -n ’/PATTERN/ =’ <file
sed -n ’$=’ <file
sed -n ’/begin/,/end/ =’
W poprzednich notatkach wytłumaczaliśmy sposób działania seda. Istotną rolę odkrywa
pattern buffer (pattern space), czyli bufor w którym sed trzyma tekst, na którym operuje.
Domyślny przepływ danych to powtórzenia następującego cyklu. sed rozpoczyna wczytując
pierwszą linię z wejścia do pattern buffer. Wtedy powtarza kolejne trzy kroki.
(i) Aplikacja kolejnych komend do bieżącej zawartości pattern buffer. Uwaga: komendy
zazwyczaj zmieniają pattern buffer (np. komenda s).
(ii) Po aplikacji ostatniej komendy drukowanie bieżącej zawartości pattern buffer na
wyjście (drukowanie można wyłączyć opcją -n).
(iii) Opróżnienie pattern buffer i wczytanie kolejnej linii z wejścia do pattern buffer.
Strona 1/9
Środowisko programisty
Zestaw 4
Semestr zimowy 2016/2017
Kraków, 3,8,9 listopada 2016
Komendy n, N, d, D, p i P pozwalają na bezpośredni dostęp do pattern buffer. Krótki opis
działania tych komend jest uzupełniony tabelką, która pokazuje przykładowe użycie.
Komenda n drukuje pattern buffer na wyjściu (chyba, że opcja -n użyta), opróżnia pattern
buffer i wczytuje do pattern buffer nową linię z wejścia. Komenda N nie drukuje pattern
buffer, nie opróżnia pattern space. Czyta ona kolejną linię z wejścia i dokleja ją do
aktualnego pattern buffer poprzedzając ją znakiem \n.
Komenda d opróżnia pattern buffer. Następnie rozpoczyna nowy cykl: czyta nową linię
z wejścia, umieszcza tę linię w pattern buffer, itd. Komenda D usuwa z pattern buffer
pierwszą linię (to jest ciąg znaków do pierwszego \n), nie wczytuje nowej linii do pattern
buffer i rozpoczyna nowy cykl.
Komenda p drukuje cały pattern buffer. Komenda P drukuje pierwszą linię pattern buffer,
do znaku nowej linii: \n. Te komendy nie zmieniają zawartości pattern buffer.
W poniższej tabelce opcja <default> zależy od użycia flag, przede wszystkim flagi -n.
Pattern Space
AB
AB
AB
AB
AB
AB
AB\nCD
AB\nCD
AB\nCD
AB\nCD
AB\nCD
AB\nCD
Next Input
CD
CD
CD
CD
CD
CD
EF
EF
EF
EF
EF
EF
Command
n
N
d
D
p
P
n
N
d
D
p
P
Output
New Pattern Space New Text Input
<default> CD
EF
AB\nCD
EF
EF
EF
AB
AB
CD
AB
AB
CD
<default> EF
GH
AB\nCD\nEF
GH
EF
GH
CD
EF
AB\nCD
AB\nCD
EF
AB
AB\nCD
EF
Przykład: do każdej linii kończącej się znakiem # dokleja następną linię (ale do tej kolejnej
nic nie jest doklejane):
#!/bin/bash
sed ’
# look for a "#" at the end of the line
/#$/ {
# Found one - now read in the next line
N
# delete the "#" and the new line character,
s/#\n//
}’
Przykład: kasuje wszystko pomiędzy ONE i TWO pojawiających się odpowiednio po sobie
Strona 2/9
Środowisko programisty
Zestaw 4
Semestr zimowy 2016/2017
Kraków, 3,8,9 listopada 2016
w tej samej lub sąsiadujących liniach. Pytanie na zrozumienie: czy semantyka skryptu
zmieni się jeśli zamiast komendy D napiszemy d?
#!/bin/bash
sed ’
/ONE/ {
# append the next line
N
# look for "ONE" followed by "TWO"
/ONE.*TWO/ {
# delete everything between
s/ONE.*TWO/ONE TWO/
# print
P
# then delete the first line
D
}
}’
Przykład: doklejenie do początku każdej linii jej numeru:
#!/bin/bash
sed ’=’ file | \
sed ’{
N
s/\n/ /
}’
Poza pattern space sed ma jeszcze jedno miejsce w którym możemy zapamiętać i edytować
linie przeczytane z wejścia, mianowicie hold buffer. Jest pięć komend obsługujących hold
buffer: x, h, H, g oraz G.
Komenda x zamienia wartości pattern buffer i hold buffer. Jaki jest zatem efekt polecenia
sed ’x’? Komenda h kopiuje pattern buffer do hold buffer. Komenda H dokleja pattern
buffer do końca hold buffer poprzedzając go znakiem \n. Komenda g kopiuje hold buffer
do pattern buffer. Komenda G dokleja hold buffer do końca pattern buffer poprzedzając
go znakiem \n.
Poniżej prezentujemy implementację grepa, który oprócz linii w której występuje szukany
wzorzec wypisuje również linię poprzednią oraz następną. Kolejne trójki oddzielane są linią
zawierającą trzy myślniki: ---. Niestety, ta implementacja przeocza niektóre wystąpienia
...
#!/bin/bash
Strona 3/9
Środowisko programisty
Zestaw 4
Semestr zimowy 2016/2017
Kraków, 3,8,9 listopada 2016
# if there is only one argument, exit
case $# in
1);;
*) echo "Usage: $0 pattern";exit;;
esac;
# I hope the argument doesn’t contain a /
sed -n ’
’/$1/’ !{
# put the non-matching line in the hold buffer
h
}
’/$1/’ {
# found a line that matches
# add the next line to the pattern space
N
# exchange the previous line with the
# 2 in pattern space
x
# now add the two lines back
G
# and print it.
p
# add the three hyphens as a marker
a\
--# remove first 2 lines
s/.*\n.*\n\(.*\)$/\1/
# and place in the hold buffer for next time
h
}’
W zasadzie, choć nie jest to zbyt poręczne, można w sed zapisać dowolny program
(Zaimplementować Maszynę Turinga). W tym celu mamy do dyspozycji instrukcję skoku
oraz skoku warunkowego. Komenda b przeskakuje do miejsca wskazanego przez podaną
etykietę. Jeśli po komendzie b nie ma żadnej etykiety przeskakujemy do końca cyklu.
Komenda t przeskakuje do podanej etykiety jeśli ostatnio wykonane podstawienie zmodyfikowało
pattern buffer.
Przykład: jeśli paragraf zawiera szukaną frazę to drukujemy cały paragraf. Paragrafy
oddzielone są pustymi liniami. Pierwszy skrypt bez użycia komendy b, drugi z użyciem
b. Dlaczego drugi skrypt jest lepszy?
Strona 4/9
Środowisko programisty
Zestaw 4
Semestr zimowy 2016/2017
Kraków, 3,8,9 listopada 2016
#!/bin/bash
sed -n ’
/^$/ !{
H
}
/^$/ {
x
/’$1’/ p
}
$ {
x
/’$1’/ p
}’
#!/bin/bash
sed -n ’
# if an empty line, check the paragraph
/^$/ b para
# else add it to the hold buffer
H
# at end of file, check paragraph
$ b para
# now branch to end of script
b
# this is where a paragraph is checked for the pattern
:para
# return the entire paragraph
# into the pattern space
x
# look for the pattern, if there - print
/’$1’/ p
’
Przykład użycie komendy t: z podanego tekstu usuń nawiasy ( i ) występujące w podanej
kolejności i przedzielone co najwyżej białymi znakami.
sed ’s/([ ^I]*)//g’
A co jeśli chcemy wykonywać to czyszczenie tak długo dopóki nie będzie takich nawiasów
(czyli dla ciągu ((())) wynikowy ciąg jest pusty)?
#!/bin/bash
sed ’
Strona 5/9
Środowisko programisty
Zestaw 4
Semestr zimowy 2016/2017
Kraków, 3,8,9 listopada 2016
:again
s/([ ^I]*)//
t again
’
Perlowe Wyrażenia regularne
PCRE, czyli Perl Compatible Regular Expressions, to niezależna implementacja wyrażeń
regularnych, inspirowana wyrażeniami regularnymi w języku Perl. Pierwotnie PCRE dostępne
były jako biblioteka w języku C. Obecnie wyrażeń tych możemy używać nawet z poleceniem
grep (opcja -P) i tak będziemy ich używać podczas naszych zajęć.
Przewagę PCRE prezentujemy poprzez przykłady. Omawiając przykłady wprowadzimy
składnię nowych funkcjonalności.
Przykład. Napisz wyrażenie regularne dopasowujące się jedynie do siedmioliterowych
wyrazów w słowniku scrabblisty w których występuje spójny podciąg myk.
Używając standardowych wyrażeń regularnych można to zrobić tak:
grep -E ’^(myk....|.myk...|..myk..|...myk.|....myk)$’ scrabble.txt
Nie jest to w pełni satysfakcjonujące rozwiązanie i łatwo wyobrazić sobie zapytania w
których długość wyrażenia do napisania osiąga rozmiar uniemożliwiający jego wygodne
stosowanie. Oto alternatywne rozwiązanie używające PCRE:
grep -P ’(?=^.{7}$).*myk.*’ scrabble.txt
Powyższe wyrażenie regularne używa jednego z wariantów lookarounds. Są to polecenia,
które możemy wstawić do wyrażenia regularnego, powodujące, że blok przed (lub po)
poleceniu może być dopasowany tylko wtedy gdy warunek w poleceniu jest spełniony.
W powyższym przykładzie program najpierw szuka wyrażenia dopasowującego się do
.*myk.*, a następnie sprawdza warunek w nawiasie, który w naszym przypadku spawdza,
czy odpowiednia linia ma 7 liter.
Oto lista wszystkich lookarounds z przykładami. (Zdecydowaliśmy się nie tłumaczyć nazw
lookarounds na polski.)
(i) lookahead : ’\d+(?= złotych)’
Przykład dopasowania: 100 w ciągu 100 złotych. Wyrażenie /d dopasowuje się
do dowolnej cyfry. W konsekwencji program dopasuje wyrażenie regularne: \d+
do dowolnego niepustego ciągu cyfr pod warunkiem, że poprzedza on wyrażenie:
(spacja)złotych.
(ii) negative lookahead : \d+(?! złotych)
Przykład dopasowania: 100 w ciągu 100 hrywien, ale nie w ciągu 100 złotych.
Strona 6/9
Środowisko programisty
Zestaw 4
Semestr zimowy 2016/2017
Kraków, 3,8,9 listopada 2016
Zastanów się, czy w ciągu 100 złotych coś zostanie dopasowane?
(iii) lookbehind (?<=czarny )kot
Przykład dopasowania: kot w ciągu czarny kot
(iv) negative lookbehind (?<!czarny )kot
Przykład dopasowania: kot w ciągu mały kot
Przykład. Napisz wyrażenie regularne dopasowujące się do linii zawierających dokładnie
jedno wystąpienie ciągu kosmos.
Gdyby chodziło o dokładnie jedno wystąpienie, powiedzmy, litery k to można by to zrobić
przy pomocy następującego wyrażenia:
^[^k]*k[^k]*$
Jednak jeśli chodzi o dokładne wystąpienie ciągu kosmos, następujące wyrażenie jest jedną
z najprostszych opcji:
^(?=.*kosmos)(?!.*kosmos.*kosmos).*$
Przykład. Napisz wyrażenie regularne, które dopasowuje się do tekstu pomiędzy znacznikami
begin i end.
Zacznijmy od bardzo naiwnego wyrażenia regularnego i spróbujmy je naprawiać krok po
kroku.
begin.*end
Zauważmy najpierw, że wywołanie
echo "aaa begin end end aaa" | grep ’begin.*end’
dopasuje się do tekstu begin end end. Tymczasem w specyfikacji mamy, że blok wyróżniony
(który chcemy dopasować) powinien być zamknięty pierwszym ciągiem end po rozpoczynającym
ciągu begin. Błąd wynika z tego, że wyrażenia regularne są domyślnie interpretowane
zachłannie. W PCRE możemy zmieniać to zachowanie.
begin.*?end
Znacznik ? w sekwencji .*? interpretujemy, jako zmianę działania .* z dopasowywującego
jak najdłuższy ciąg, na dopasowywujące jak najkrótszy ciąg pasujący do wyrażenia. Zatem
interpreter w tym przypadku będzie szukać najkrótszego ciągu po którym następuje ciąg
end.
Oto zwięzły zestaw przykładów operujący podobnymi znacznikami:
(i) A++, dopasowuje się zachłannie do jednego lub więcej A i sprawdza jedynie maksymalne
dopasowanie nie próbując mniejszych; takie wyrażenie nazywamy zaborczym;
(ii) A+, dopasowuje się zachłannie do jednego lub więcej A, zaczyna sprawdzanie od
maksymalnego dopasowanie ale w razie niepowodzenia sprawdza też kolejno mniejsze
Strona 7/9
Środowisko programisty
Zestaw 4
Semestr zimowy 2016/2017
Kraków, 3,8,9 listopada 2016
(iii)
(iv)
(v)
(vi)
dopasowania;
’A+.’ dopasuje się do AAA (ponieważ po dopasowaniu AAA nie ma kolejnego znaku,
zostanie dopasowany ciąg AA, a ostatnie A potraktowane jako znak znajdujący się na
końcu wzorca), natomiast ’A++.’ nie dopasuje się do AAA (nie zostanie sprawdzone
dopasowanie mniejsze niż AAA).
znacznik ++ najczęściej jest używany w sytuacji gdy mamy pewność, że jeśli jest
dopasowanie to powinno być maksymalne. Na przykład kiedy chcemy dopasować
liczbę, czyli ciąg cyfr i następujące po spacji słowo to efektywnie będzie użyć wyrażenia
[0-9]++ [a-z]++;
znacznik ++ przydatny jest również, gdy chcemy zapamiętać maksymalne dopasowanie
jako grupę do której później się odwołamy. Patrz przykład poniżej.
A*+ działa analogicznie zaborczo, pozwala jednak dopasować się do zero lub więcej
liter A
Aby lepiej uzmysłowić działanie ++ przypomnijmy sobie przykład z negative lookahead :
\d+(?! złotych). Wzorzec ten nie dopasuje 100 w ciągu 100 złotych, ale dopasuje 10,
ponieważ nie zabraniamy aby po dopasowaniu wystepował ciąg 0 złotych. Najprostszym
i najbardziej eleganckim sposobem poprawienia tego wzorca jest użycie właśnie zaborczego
modyfikatora: \d++(?! złotych).
Wróćmy do przykładu ze znacznikami begin i end. Kolejnym zasadniczym problemem
jest to, że obecne wyrażenie regularne nie wyszukuje bloków rozpiętych na więcej niż
jednej linii. Tak naprawdę to wina zarówno polecenia grep, które przetwarza wejście linia
po linii, niezależnie, jak i samego wyrażenia regularnego, w którym znak . nie dopasowuje
się do znaku końca linii \n.
W związku z powyższym możemy poprawić dwie rzeczy:
(i) wywołać grep z opcją -z, dzięki czemu wejście dopasowywane będzie do wzorca w
jednym przejściu, a nie linia po linii;
(ii) wyrażenie regularne powinno być poprzedzone modyfikatorem (?s), dzięki czemu
znak . będzie dopasowywać się także do znaku \n.
Zatem poprawione wywołanie polecenia to:
grep -Pz ’(?s)begin.*?end’
Następnym problemem do rozwiązania jest to, że wzorzec dopasowuje się do bloków
wyróżnionych wraz ze znacznikami ograniczającymi. Jeśli chcemy mieć dopasowanie jedynie
do znaków wewnątrz bloku to powinniśmy skorzystać z lookarounds.
grep -Pz ’(?s)(?<=begin).*?(?=end)’
Przykład. Na wejściu otrzymujemy linie zawierające ciągi binarne (jedyne znaki w linii
to 0 i 1). Mamy znaleźć wszystkie wystąpienia ciągu 11 takie że pierwsza jedynka jest na
nieparzystym miejscu (pierwszy znak w linii jest na pierwszym miejscu).
Strona 8/9
Środowisko programisty
Zestaw 4
Semestr zimowy 2016/2017
Kraków, 3,8,9 listopada 2016
Oto pierwsza próba rozwiązania:
grep -P ’(..)*?11’
Wyrażenie dopasuje ciąg 11, ale dopasowane zostaje także wszystko przed 11. Dlatego
kolejna próba rozwiązania będzie wyglądać tak:
grep -P ’(..)*?\K11’
Komenda \K powoduje, że ciąg który się dopasował do danego momentu do wzorca jest
wymazany z dopasowania.
Jak już wiemy, w wyrażeniach regularnych blok zaznaczony nawiasami okrągłymi zapamiętuje
nam tekst dopasowany do bloku, do którego później możemy się odwołać poprzez \1,. . . ,\9.
Okazuje się, że w PCRE blok możemy również traktować jako funkcję, którą możemy
wywołać w wyrażeniu regularnym.
Przykład. Wyszukaj wszystkie palindromy w słowniku scrabblisty. Używając standardowych
wyrażeń regularnych potrafiliśmy wyszukać palindromy określonej długości np. ’^(.)(.).\2\1$’
dopasowuje palindromy pięcioliterowe.
W PCRE możemy łatwo wyszukać wszystkie palindromy:
grep -P ’^((.)(?1)?\2|.)$’
Instrukcja (?1) powoduje wywołanie funkcji oznaczonej przez pierwszy (tutaj, zewnętrzny)
blok. Procedurę umieszczoną w zewnętrznych nawiasach wywołujemy rekurencyjnie dla
słowa znajdującego się pomiędzy pierwszą i ostatnią literą. Jeśli rozpatrywane słowo ma
mniej niż dwie litery dopasuje się do wyrażenia . znajdującego się po |.
Instrukcja (?R) powoduje wywołanie całego wyrażenia jako funkcji. Komenda:
grep -P ’(\w)(?:(?R)|\w?)\1’
to inny sposób rozpoznawania palindromów. Sekwencja: \w dopasowuje się do dowolnego
znaku, który może występować w słowie. Część: ?:(?R) pozwala na wywołanie rekurencyjne.
Jeśli ono się nie powiedzie przechodzimy do drugiej części ORa: | i staramy się dopasować
pozostałą część słowa do \w?. To ostatnie wyrażenie dopasowuje się do pojedynczej litery
lub znaku pustego.
Ostatnie wywołanie znajdzie nam również linie, które nie są palindromami, ale zawierają
palindromy jako podsłowa. Dlaczego następująca modyfikacja nie będzie działać?
grep -P ’^(\w)(?:(?R)|\w?)\1$’
Strona 9/9

Podobne dokumenty