Home Dokumentacje Sed w przykładach, część druga
06 | 12 | 2019
Sed w przykładach, część druga
Wpisany przez Daniel Robbins   

Uwaga: Oryginalna wersja tego artykułu została opublikowana w IBM developerWorks i jest własnością Westtech Information Services. Poniższy dokument jest poprawioną przez zespół GDP wersją oryginalnego tekstu i nie jest już aktualizowany.

Sed w przykładach, część druga

Daniel Robbins Autor
Kuba Bożanowski Tłumaczenie

Zaktualizowano 9 października 2005

1. Jak jeszcze lepiej wykorzystać edytor tekstowy systemu UNIX

Podstawianie!

Przyjrzyjmy się jednej z najbardziej przydatnych komend edytora sed, podstawieniu. Przy jej pomocy możemy zastąpić określony ciąg znaków lub dopasowane wyrażenie regularne innym ciągiem znaków. Oto przykład najbardziej elementarnego zastosowania tej komendy:

Listing 1.1: Najbardziej elementarne zastosowanie komendy podstawienia

$ sed -e 's/cos/cosinnego/' mojplik.txt

Powyższe polecenie wypisze zawartość pliku mojplik.txt na standardowe wyjście, zastępując w każdej linii pierwsze wystąpienie napisu "cos" (o ile w ogóle wystąpi) napisem "cosinnego". Zauważmy, że napisałem o pierwszym wystąpieniu w każdej linii, pomimo iż zwykle nie o to nam chodzi. Najczęściej gdy wykonujemy podstawienie napisu, chcemy tego dokonać globalnie. Oznacza to, że chcę zastąpić wszystkie wystąpienia napisu w każdej linii, w ten sposób:

Listing 1.2: Zamiana wszystkich wystąpień w każdej linii

$ sed -e 's/cos/cosinnego/g' mojplik.txt

Dodatkowa opcja 'g' po ostatnim ukośniku mówi sedowi, aby wykonał globalne podstawienie.

Powinniśmy wiedzieć jeszcze kilka rzeczy o komendzie s///. Po pierwsze, mówiliśmy wyłącznie o komendzie; w powyższych przykładach nie podawaliśmy adresów. Oznacza to, że możemy użyć polecenia s/// wraz z adresem, aby kontrolować, do których wierszy będzie ona zastosowana. Robimy to w następujący sposób:

Listing 1.3: Określanie wierszy, do których komenda zostanie zastosowana

$ sed -e '1,10s/zaczarowanie/zablokowanie/g' mojplik2.txt

Powyższy przykład sprawi, że wszystkie wystąpienia wyrazu "zaczarowanie" zostaną zastąpione wyrazem "zablokowanie", ale tylko w wierszach od pierwszego do dziesiątego włącznie.

Listing 1.4: Podawanie dalszych opcji

$ sed -e '/^$/,/^END/s/wzgórza/góry/g' mojplik3.txt

W tym przykładzie wyraz "wzgórza" zostanie zastąpiony wyrazem "góry", ale tylko w tych blokach tekstu, które zaczynają się pustą linią, a kończą linią zaczynającą się od liter "END" włącznie.

Kolejną miłą rzeczą dotyczącą komendy s/// jest mnogość opcji dotyczących separatorów /. Jeśli chcemy dokonać podstawienia napisów i wyrażenie regularnie lub napis, który chcemy zmienić zawiera wiele ukośników, możemy zmienić separator, podając inny znak po literze "s". Zilustrujmy to przykładem, w którym zamienimy wszystkie wystąpienia /usr/local na /usr:

Listing 1.5: Zamiana wszystkich wystąpień napisu innym napisem

$ sed -e 's:/usr/local:/usr:g' mojalista.txt

Uwaga: W powyższym przykładzie użyliśmy dwukropka jako separatora. Jeśli musielibyśmy użyć znaku separatora w wyrażeniu regularnym, należałoby poprzedzić go odwrotnym ukośnikiem.

Pułapki wyrażeń regularnych

Do tej pory wykonywaliśmy jedynie podstawienia zwykłych napisów. Mimo iż często się to przydaje, to jednak możemy zrobić to samo dla wyrażeń regularnych. Poniższa komenda znajdzie frazę rozpoczynającą się znakiem "<" i kończącą się znakiem ">", posiadającą dowolną ilość znaków pomiędzy nimi. Fraza ta zostanie skasowana (zastąpiona pustym napisem):

Listing 1.6: Kasowanie podanej frazy

$ sed -e 's/<.*>//g' mojplik.html

Jest to dobry początek skryptu, który usunie tagi języka HTML z pliku, jednakże nie zadziała dobrze ze względu na pewną właściwość wyrażeń regularnych. Mianowicie gdy sed szuka wyrażenia regularnego w linii, znajduje najdłuższe możliwe dopasowanie w tej linii. Nie było to problemem w poprzednim artykule o sedzie, ponieważ używaliśmy komend d i p, które tak czy inaczej skasują lub wypiszą całą linię. W przypadku komendy s/// nie odpowiada nam to, ponieważ cały dopasowany fragment tekstu zostanie zastąpiony przez docelowy napis, a w tym przypadku skasowany. Oznacza to, że powyższy przykład zamieni następującą linię:

Listing 1.7: Przykładowy kod HTML

<b>Właśnie</b> o to <b>mi</b> chodziło.

W tę:

Listing 1.8: Niepożądany wynik

chodziło.

Zamiast w poniższą, czyli w to, co chcieliśmy osiągnąć:

Listing 1.9: Pożądany wynik

Właśnie o to mi chodziło.

Na szczęście da się temu zaradzić. Zamiast wyrażenia regularnego, które znajdzie "dowolną ilość znaków, poprzedzoną znakiem '<' i zakończoną znakiem '>'" musimy użyć takiego, które odnajdzie "dowolną ilość znaków różnych od znaku '>', poprzedzoną znakiem '<' i zakończoną znakiem '>'". W ten sposób znajdziemy najkrótsze możliwe dopasowanie, zamiast najdłuższego. Nowe polecenie powinno wyglądać tak:

Listing 1.10:

$ sed -e 's/<[^>]*>//g' mojplik.html

W powyższym przykładzie wyrażenie "[^>]" oznacza "znak różny od '>'", a symbol "*" uzupełnia je o znaczenie "zero lub więcej znaków różnych od '>'". Warto wypróbować to polecenie na kilku przykładowych plikach html, przeglądając wyniki za pomocą programu more.

Dopasowanie większej ilości znaków

Składnia wyrażenia regularnego "[ ]" oferuje jeszcze kilka możliwości. Możemy określić zakres znaków za pomocą symbolu "-", o ile nie znajduje się on na pierwszym lub ostatnim miejscu:

Listing 1.11: Podawanie zakresu znaków

'[a-x]*'

W ten sposób wyszukamy zero lub więcej znaków, dopóki wszystkie należeć będą do zbioru "a","b","c"..."v","w","x". Oprócz tego dysponujemy klasą znakową "[:space:]" do znajdywania odstępu. Poniżej znajduje się prawie kompletna lista dostępnych klas znakowych.

Klasa znakowa Opis
[:alnum:] Znaki alfanumeryczne [a-z A-Z 0-9]
[:alpha:] Znaki alfabetyczne [a-z A-Z]
[:blank:] Spacje lub tabulatory
[:cntrl:] Dowolny znak kontrolny
[:digit:] Cyfry [0-9]
[:graph:] Znaki drukowalne (bez odstępów)
[:lower:] Małe litery [a-z]
[:print:] Znaki drukowalne z odstępami
[:punct:] Znaki drukowalne za wyjątkiem odstępów, liter i cyfr
[:space:] Wszystkie znaki odstępu
[:upper:] Duże litery [A-Z]
[:xdigit:] Cyfry w systemie szesnastkowym [0-9 a-f A-F]

Używanie klas znakowych wszędzie tam, gdzie to możliwe jest wysoce pożądane, ponieważ znakomicie dopasowują się one do innych niż angielskie zestawów znaków (uwzględniając znaki akcentowane, itd).

Zaawansowane podstawianie

Do tej pory przyjrzeliśmy się prostym i w miarę złożonym podstawieniom, ale sed potrafi jeszcze więcej. Możemy odnosić się do dowolnej części lub całości tekstu odnalezionego przez wyrażenie regularne i użyć jej do stworzenia podstawianego napisu. Przykładowo, załóżmy że chcemy odpowiedzieć na wiadomość. Poniższa komenda poprzedzi każdą linię wyrażeniem "ralph powiedział: ":

Listing 1.12: Poprzedzanie każdej linii określonym napisem

$ sed -e 's/.*/ralph powiedział: &/' oryginalnawiadomosc.txt

Oto wynik jej działania:

Listing 1.13: Efekt działania powyższej komendy

ralph powiedział: Cześć Jim,
ralph powiedział:
ralph powiedział: Bardzo podoba mi się ta lekcja seda!
ralph powiedział:

W powyższym przykładzie wykorzystaliśmy znak "&" w podstawianym napisie. Mówi on sedowi, aby wstawić w tym miejscu cały tekst znaleziony przez wyrażenie regularne. Tak więc cokolwiek zostało znalezione przez ".*" (największa grupa zera lub więcej znaków w linii lub cała linia) może zostać podstawione w dowolnym miejscu podstawianego tekstu nawet kilka razy. Całkiem nieźle, ale sed potrafi jeszcze więcej.

Dyskretny urok nawiasów poprzedzonych odwrotnymi ukośnikami

Komenda s/// pozwala nam na definiowanie regionów w wyrażeniu regularnym, do których możemy się następnie odwoływać w podstawianym tekście. W ramach przykładu przyjrzyjmy się następującemu tekstowi:

Listing 1.14: Przykładowy tekst

bla ble bli
ele mele dudki
ala ma kota
kot ma alę

Załóżmy teraz, że chcemy napisać skrypt w sedzie, który zamieni "ele mele dudki" na "Victor ele-mele Von dudki" i tak dalej. Aby tego dokonać najpierw powinniśmy napisać wyrażenie regularne, które odnajdzie nasze trzy wyrazy oddzielone spacjami:

Listing 1.15: Odpowiednie wyrażenie regularne

'.* .* .*'

Właśnie tak. Następnie zdefiniujmy regiony, umieszczając wokół każdego interesującego nas miejsca nawiasy poprzedzone wstecznym ukośnikiem:

Listing 1.16: Definiowanie regionów

'\(.*\) \(.*\) \(.*\)'

Powyższe wyrażenie regularne zadziała indentycznie jak pierwsze. Jedyną różnicą będzie fakt, iż definiuje ono trzy logiczne regiony, do których będziemy mogli odnieść się w naszym podstawianym napisie. Oto ostateczny skrypt:

Listing 1.17: Ukończony skrypt

$ sed -e 's/\(.*\) \(.*\) \(.*\)/Victor \1-\2 Von \3/' mojplik.txt

Jak widać, odnosimy się do każdego obszaru ograniczonego nawiasami za pomocą wyrażenia "\x", gdzie x jest numerem regionu, zaczynając od jedynki. Efekt działania skryptu będzie następujący:

Listing 1.18: Wynik działania powyższej komendy

Victor bla-ble Von bli
Victor ele-mele Von dudki
Victor ala-ma Von kota
Victor kot-ma Von alę

W miarę jak poznajemy seda coraz bliżej, możliwe staje się dokonywanie skomplikowanych operacji na tekście przy minimum wysiłku. Warto zastanowić się nad rozwiązaniem powyższego problemu za pomocą dowolnego języka skryptowego -- czy udałoby nam się równie łatwo zmieścić rozwiązanie w jednej linijce?

Zamieszanie w komendach

Gdy zaczynamy tworzyć bardziej złożone skrypty, potrzebujemy możliwości wpisywania więcej niż jednej komendy naraz. Można tego dokonać na kilka sposobów. Po pierwsze, możemy rozdzielić komendy średnikami. Na przykład ten zestaw poleceń używa komendy "=", która każe sedowi wypisać numer linii, oraz komendy p, która specjalnie instruuje seda, aby wypisał linię (ponieważ pracujemy w trybie "-n"):

Listing 1.19: Pierwszy sposób, średniki

$ sed -n -e '=;p' mojplik.txt

Gdy podajemy dwie lub więcej komend, każda z nich jest zastosowana (po kolei) do każdej linii pliku. W powyższym przykładzie na pierwszej linii zostanie użyta komenda "=", a następnie p. Sed wówczas przechodzi do drugiej linii i proces powtarza się. Pomimo iż średnik okazał się być przydatny, w niektórych sytuacjach nie zadziała. Alternatywą będzie użycie dwóch parametrów -e w celu podania dwóch osobnych komend:

Listing 1.20: Drugi sposób, wiele parametrów -e

$ sed -n -e '=' -e 'p' mojplik.txt

Jednak gdy tylko dojdziemy do bardziej złożonych poleceń dodawania i wstawiania, nawet wiele parametrów "-e" nam nie pomoże. Wówczas najlepiej zrobimy zapisując wszystkie komendy w osobnym pliku, a następnie wywołując go parametrem -f:

Listing 1.21: Trzeci sposób, zewnętrzny plik zawierający komendy

$ sed -n -f mojekomendy.sed mojplik.txt

Ten sposób, choć mniej wygodny, nigdy nas nie zawiedzie.

Wiele komend wykonanych na jednym adresie

Czasem zajdzie potrzeba wykonania kilku poleceń na jednym przedziale. Szczególnie jest to przydatne gdy chcemy wykonać wiele komend s/// w celu przekształcenia wyrazów lub składni w pliku źródłowym. Aby wykonać wiele komend na jednym adresie zapiszmy je w pliku i użyjmy znaków "{ }" do pogrupowania ich:

Listing 1.22: Podawanie wielu komend dla jednego adresu

1,20{
        s/[Ll]inux/GNU\/Linux/g
        s/samba/Samba/g
        s/posix/POSIX/g
}

Powyższe polecenia zastosują trzy podstawienia w liniach od pierwszej do dwudziestej włącznie. Jako adresów moglibyśmy również użyć wyrażenia regularnego lub połączyć jedno z drugim:

Listing 1.23: Połączenie obu sposobów

1,/^END/{
        s/[Ll]inux/GNU\/Linux/g
        s/samba/Samba/g
        s/posix/POSIX/g
        p
}

W powyższym przykładzie wszystkie komendy między znakami "{ }" zostaną użyte na tekście od pierwszej linii aż po linię, która zaczyna się literami "END" lub, w przypadku braku takowej, aż po koniec pliku.

Dołączanie, wstawianie i zmienianie linii

Teraz gdy już piszemy skrypty seda w osobnych plikach, możemy wykorzystać polecenia dołączania, wstawiania i zmiany linii. Polecenia te wstawią wiersz po aktualnej linii, przed aktualną linią lub zastąpią ją. Można ich także używać do wstawiania wielu wierszy jednocześnie. Komendy wstawiania używa się w następujący sposób:

Listing 1.24: Użycie komendy wstawiania wiersza

i\
Ten wiersz zostanie wstawiony przed każdą linią

Jeśli nie podamy adresu dla tej komendy, zostanie ona zastosowana do każdej linii, a wynik jej działania będzie podobny do poniższego:

Listing 1.25: Efekt działania powyższego polecenia

Ten wiersz zostanie wstawiony przed każdą linią
oto linia 1
Ten wiersz zostanie wstawiony przed każdą linią
oto linia 2
Ten wiersz zostanie wstawiony przed każdą linią
oto linia 3
Ten wiersz zostanie wstawiony przed każdą linią
oto linia 4

Jeśli zechcemy wstawić więcej niż jeden wiersz przed aktualną linią, wystarczy że dopiszmy je, dodając odwrotny ukośnik na końcu każdego poprzedniego wiersza:

Listing 1.26: Wstawianie wielu wierszy przed aktualną linią

i\
wstaw ten wiersz\
i ten\
i ten\
o, nie zapomnijmy tez o tym.

Komenda dołączania działa podobnie, tylko dołącza wiersz lub wiersze po aktualnej linii. Oto przykład:

Listing 1.27: Dołączanie wiersza po aktualnej linii

a\
proszę wstawić ten wiersz po każdej linii. Z góry dziękuję! :)

Z kolei polecenie "zmień linię" podmieni ją na nową.

Ze względu na to, iż komendy dołączania, wstawiania i zamiany linii muszą być podawane w kilku wierszach, należy wpisywać je w plikach tekstowych i podawać sedowi za pomocą parametru "-f". Pozostałe metody wpisywania komend zaowocują problemami.

W następnym odcinku

W następnym artykule, który będzie już ostatnim na temat seda, ukazane zostaną znakomite przykłady z życia wzięte na zastosowanie seda do wielu różnych zadań. Nie tylko dowiemy się co skrypty robią, ale też w jaki sposób działają. Z pewnością zaowocuje to wieloma pomysłami na zastosowanie seda w rozmaitych projektach. Do zobaczenia w następnym odcinku!

2. Zasoby

Przydatne odnośniki

 
Linki sponsorowane

W celu realizacji usług i funkcji na witrynach internetowych ZUI "ELPRO" stosujemy pliki cookies. Korzystanie z witryny bez zmiany ustawień dotyczących plików cookies oznacza, że będą one zapisywane w urządzeniu wyświetlającym stronę internetową. Więcej szczegółów w Polityce plików cookies.

Akceptuję pliki cookies z tej witryny.