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