Spis treści Skorowidz Poziom główny Poziom nadrzędny Wstecz Dalej Zadania ©

Filtry do przetwarzania plików znakowych.
Praca potokowa

Any tool should be useful in the expected way, but a truly great tool lends itself to uses you never expected.

Każde narzędzie powinno być użyteczne w przewidziany sposób, jednak naprawdę wielkie narzędzie znajduje nieprzewidziane zastosowania.

Eric S. Raymond, The Cathedral and the Bazaar (Katedra i bazar) (1997)

Wprowadzenie

Dobrze zaprojektowany system operacyjny powinien posiadać zestaw programów narzędziowych, które operują na danych zapisanych w plikach znakowych przekształcając je w ściśle określony sposób, z pozostawieniem użytkownikowi decyzji co do sposobu wykorzystania danych wynikowych. Zestaw takich narzędzi można więc przyrównać do warsztatu wyposażonego w śrubokręty, klucze nasadowe czy szczypce, które każdy może wykorzystać do realizacji swoich szczególnych celów w sposób, jaki uzna za stosowny. Znajomość zasad korzystania z narzędzi umożliwia ich twórcze wykorzystanie przy realizacji nowych zadań.

Przedstawione w tym rozdziale uwagi dotyczące zasad uruchamiania programów oraz sterowania wejściem i wyjściem odnoszą się do wszystkich systemów operacyjnych typu DOS, MacOS, UNIX i Windows, a także do kilku innych grup systemów.

Przy omawianiu filtrowania plików znakowych skupiono się na filtrach opracowanych dla systemu UNIX, ponieważ stanowią absolutną klasykę i tworzą spójny zestaw silnych narzędzi. Z zestawu tego korzystać mogą użytkownicy wszystkich współczesnych systemów operacyjnych. Nie jest jednak wykluczone, że poszczególne prace da się wykonać także za pomocą nieco innych narzędzi zależnych od systemu operacyjnego używanego akurat przez czytelnika.

Spośród omawianych niżej programów, more i sort są standardowymi elementami wszystkich wspomnianych systemów; programy cat, cut, head, tail, join, split, paste, grep, sed, tr, xargs, uniq i comm są elementami systemów UNIX i GNU, w systemach DOS i Windows można je zainstalować; program less jest poleceniem systemu GNU, w innych systemach można go zainstalować; polecenie type jest odpowiednikiem UNIXowego programu cat w systemach DOS i Windows.

Procesor poleceń

Sekwencyjny procesor poleceń (shell) to program, udostępniający wiersz poleceń i przejmujący kontrolę nad poleceniami wydawanym przez użytkownika. Polecenie składa się z komendy i jej parametrów, rozdzielonych spacjami. Każdy procesor poleceń rozpoznaje pewien ograniczony zbiór komend dotyczących głównie zarządzania systemem plików i własnej konfiguracji. Są to tzw. komendy wewnętrzne. Inne polecenia, zwane zewnętrznymi, są traktowane jak nazwy programów, które należy uruchomić. Według ogólnie obowiązującej zasady polecenie zewnętrzne jest nazwą pliku wykonywalnego tego programu. Plik wykonywalny jest wyszukiwany w systemie plików kolejno w katalogach zadanych w konfiguracji systemu jako „ścieżki poszukiwań”. W systemach DOS/Windows w pierwszej kolejności sprawdzany jest katalog bieżący. W systemach UNIX katalog bieżący nie jest przeszukiwany, chyba że symbol . został podany jako jedna ze ścieżek poszukiwań. Po napotkaniu pierwszego pliku wykonywalnego o podanej nazwie system przerywa wyszukiwanie i uruchamia go. W prawidłowo skonfigurowanym systemie operacyjnym obszar poszukiwania powinien obejmować wszystkie katalogi, z których programy mają być uruchamiane przez podanie krótkiej, a nie pełnej nazwy. Dane o ścieżkach przechowuje zmienna środowiskowa PATH, określana w skryptach konfiguracyjnych.

W systemach DOS i Windows procesor poleceń nosi nazwę command.com, cmd.exe lub powershell.exe. W systemach UNIX użytkownik ma do wyboru kilka różnych procesorów (m.in. sh, csh, bash, ksh); oferują one znacznie bogatsze możliwości.

Wejście i wyjście standardowe

Podczas pracy proces może pobierać i wysyłać dane. Dla umożliwienia współpracy zarówno z użytkownikiem–osobą, jak klientem–programem zlecającym wykonanie pewnych czynności, wiele programów pobiera dane z urządzenia określanego jako standardowy plik wejściowy. Dane ze standardowego wejścia mogą być tylko odczytywane po kolei, bez możliwości powrotu do danych już odczytanych. Jego odpowiednikiem na „drugim końcu” procesu przetwarzania jest standardowy plik wynikowy, zwany czasami w technicznym żargonie wyjściem standardowym. Do pliku wynikowego można wyłącznie wpisywać dane (zwykle wyniki, ale czasem także komunikaty dla użytkownika), bez możliwości ich odczytania ani modyfikacji po zapisaniu. Proces realizujący polecenie „nie wie” nic więcej o plikach standardowego wejścia i wyjścia. Domyślnie pliki te są identyfikowane z konsolą, zwaną inaczej terminalem, tj. zespołem klawiatura–monitor urządzenia, z którego wywołano program. Dzięki operacji zwanej przekierowaniem mogą jednak zostać skojarzone z dowolnym plikiem, w tym także ze strumieniem danych wejściowych lub wynikowych innego procesu. Mechanizm przekierowań umożliwia podjęcie decyzji o źródle danych w chwili wydawania polecenia, a nie tworzenia programu, i używania gotowych programów jako części składowych większego projektu. W systemowym wierszu poleceń do przekierowania standardowego pliku wejściowego służy operator <, zaś do przekierowania standardowego pliku wynikowego — operatory: >, >> oraz |.

Przykłady

Filtry

— Rozpocznij od początku — odpowiedział Król z największą powagą — a później czytaj, dopóki nie dotrzesz do końca; wtedy przestań czytać.

Lewis Carroll

Filtr to program przekształcający strumień danych wejściowych na strumień danych wynikowych aż do otrzymania informacji o końcu transmisji danych. Ogólny schemat pracy filtra można przedstawić następująco:

start
  przygotuj się do pracy;
  dopóki (na wejściu są dane) wykonuj (
    czytaj daną z wejścia;
    przetwórz ją;
    zapisz przetworzoną daną na wyjściu;
  );
koniec pracy.

Zasadnicza część pracy filtra jest wykonywana w pętli, kończonej dopiero po przetworzeniu wszystkich danych z wejścia. W dowolnej chwili pracy każdego filtra, w której kończy on przetwarzanie kolejnej porcji danych, prawdziwe jest zdanie:

wszystkie dane znajdujące się w pliku wejściowym od jego początku aż do danej bieżącej zostały już odczytane i przetworzone, a efekt przetwarzania został wysłany na wyjście.

Pracę filtra można zilustrować schematem blokowym widocznym poniżej.

Schemat blokowy filtra
[ Schemat blokowy filtra ]

Programy more i less: drukowanie na terminalu

Filtrem jest np. program more. Działa on zgodnie z poniższym opisem.

start
  dopóki (na wejściu są dane) wykonuj (
    pobierz wiersz danych z wejścia standardowego;
    wydrukuj odczytany wiersz na terminalu
    jeżeli po ostatnim wstrzymaniu pracy terminal został kompletnie zapełniony, to
      wstrzymaj pracę i czekaj na znak z klawiatury (nie z wejścia standardowego!), zezwalający na dalsze drukowanie;
  );
koniec pracy.

Program less, nazwany przez przekorę w opozycji do more, ma podobne przeznaczenie, lecz jest bardziej rozbudowany. Oprócz wstrzymywania wyświetlania potrafi wiele innych rzeczy, np. przewijać wydruk wstecz lub wyszukiwać żądaną frazę. (Nie mieści się więc w definicji filtra, gdyż magazynuje wszystkie odczytane dane w pamięci roboczej).

Program grep: wyszukiwanie wzorca

Ważnym filtrem jest program o nazwie grep (ang. General Regular Expressions Printing), działający z grubsza według schematu:

start
  odczytaj wzorzec i opcje z wiersza poleceń;
  dopóki (na wejściu są dane) wykonuj (
    pobierz wiersz danych z pliku wejściowego;
    jeżeli wiersz zawiera wzorzec, to
      drukuj informację o nim na wyjściu, zgodnie z opcjami wywołania (np. z numerem wiersza);
  );
  drukuj zbiorczą informację o danych zgodnie z opcjami wywołania (np. liczba wystąpień wzorca);
koniec pracy.

Na przykład polecenie grep "kartotek" *.* wydrukuje na wyjściu wiersze wszystkich plików bieżącej kartoteki, zawierające ciąg znaków „kartotek”. Polecenie grep "kartotek" *.* > wyniki nie będzie drukować danych na terminalu, lecz zapisze je w pliku tekstowym o nazwie wyniki.

Sposób korzystania z programu grep jest opisany w instrukcji użytkownika.

Wyszukiwanie wyrażeń regularnych za pomocą operacji grep
Pliki wejścioweWynik polecenia
grep "t" plik1 > plikwynikowy
Wynik polecenia
grep -c "t" plik1 plik2 > plikwynikowy
plik1:
1	jeden
10	dziesięć
2	dwa
3	trzy
31	trzydzieści jeden
4	cztery
5	pięć 
6	sześć
7	siedem
8	osiem
9	dziewięć
0	zero
plik2:
0	zero
1	one
12	twelve
11	eleven
2	two
3	three
4	four
5	five 
6	six
7	seven
8	eight
88	eighty eight
9	nine
plikwynikowy:
3	trzy
31	trzydzieści jeden
4	cztery
plikwynikowy:
plik1:3
plik2:5

Przykład

Znajdź w pliku geometra.txt wszystkie wiersze zawierające informację o punktach. Informację tę, wraz z numerami wierszy, umieść w pliku punkty.txt, wydając polecenie:

grep -n -i punkt geometra.txt > punkty.txt

(Opcja -n każe drukować numery wierszy. Opcja -i każe utożsamiać wielkie i małe litery w treści pliku).

Przykład

Znajdź w plikach bieżącej kartoteki wszystkie miejsca, w których występuje nazwisko Kowalski.

Nazwisko zawsze piszemy z wielkiej litery, a dalej małymi literami. Odmiany gramatyczne: Kowalskiego, Kowalskiemu, Kowalską mają wspólny rdzeń „Kowalsk”.

grep -n Kowalsk *.* > kowalscy.txt

(Opcja -n każe drukować numery wierszy). Oto wydruk pliku kowalscy.txt, jaki moglibyśmy otrzymać:

ANEKS.HTM:5:w osobie Adama Kowalskiego, dyrektora firmy zarządzającej
ANEKS.HTM:56:Kowalskim w dniu 15 marca 1999 roku,
ANEKS.HTM:224:mgr inż. Adam Kowalski,
INTRO.TXT:112:dyrektora Adama Kowalskiego. Jego zaangażowanie w sprawę
INTRO.TXT:135:umowy dały dyr. Kowalskiemu możliwości
INTRO.TXT:523:lecz zgodnie z wcześniejszymi ustaleniami p. Adam Kowalski nadal
UMOWA.HTM:4:w osobie Adama Kowalskiego, dyrektora firmy zarządzającej
UMOWA.HTM:136:mgr inż. Adam Kowalski,

(Zdaje się, że za każdym razem jest mowa o tej samej osobie).

Przykład

Utwórz spis tabel dokumentu o nazwie opis.html, zapisanego w języku HTML.

Do tworzenia tabel służy element table. Będziemy poszukiwać znacznika otwierającego ten element, a ściślej — jego początkowej frazy „<table”. Nawiasu „>” zamykającego znacznik nie ujmiemy w poszukiwanej frazie, by wyniki wyszukiwania zawierały także znaczniki z parametrami, takie jak „<table align="center">”, „<table border="1">” czy też „<table class="tabelka">”.

Do wykonania zadania zastosujemy program grep. Z uwagi na to, że nazwy elementów HTML mogą być pisane małymi lub wielkimi literami, użyjemy opcji -i (skrót od Ignore letter case). Potrzebujemy też numerów wierszy pliku, by móc łatwo zidentyfikować wyszukane tabele (opcja -n, ang. line Numbers):

grep -i -n "<table" opis.html > spis-tabel

Wyszukiwana fraza zawiera znak „<” traktowany w wierszu poleceń jako rozkaz przekierowania wejścia, więc ujęliśmy ją w cudzysłowy tekstowe. Oto zawartość wygenerowanego tym sposobem pliku spis-tabel:

79:<table align="center" border="1">
204:<table align="center" border="1">
296:<table align="center" border="1">

Zastosuj tę samą metodę wyszukiwania do znajdowania innych wybranych elementów (odsyłaczy hipertekstowych, hipertekstowych punktów zakotwiczenia, osadzonych obrazów, miejsc użycia danego stylu) w posiadanych przez Ciebie dokumentach HTML. A jak sprawić, by w spisie tabel pojawiły się ich tytuły (caption)?

Przykład

Utwórz spis treści dokumentu o nazwie opis.html, zapisanego w języku HTML.

Wiemy, że nagłówki rozdziałów i podrozdziałów są objęte znacznikami <h1>, <h2>, …, <h6>. Jeżeli cały tytuł (pod)rozdziału jest umieszczony w jednym wierszu pliku źródłowego, to mamy pewność, że wszystkie tytuły znajdziemy, wyszukując wiersze z tymi znacznikami.

Jeżeli znaczniki i tytuł nie mieszczą się w tym samym wierszu pliku źródłowego, to nici z prostego pomysłu, ale nie do końca. Ja sam staram się przestrzegać zasady „kod nagłówka w całości w jednym wierszu” choćby po to, by móc stosować proste narzędzia. Jednak nie martw się: grep i tak znajdzie znacznik otwierający, a podczas analizy wyników zauważysz, że tytuł jest niekompletny (np. bez znacznika zamykającego). Możliwe jednak, że do wyników dostaną się wiersze zawierające ciągi znaków <h12 lub <h1a, które nie mają nic wspólnego z poprawnymi znacznikami HTML.

Zadanie jest o stopień trudniejsze od poprzedniego, ponieważ fraza <h jest za krótka (pasuje do niej znacznik <hr, który nas nie interesuje), a kolejne poszukiwanie fraz od <h1 do <h6 skomplikuje pracę, zmuszając do porządkowania wyników. Z pomocą przychodzą wyrażenia regularne: będziemy poszukiwać wzorca <hcyfra-od-1-do-6. Na szczęście grep rozumie składnię wyrażeń regularnych.

Aby nie zapomnieć o znacznikach pisanych wielkimi literami: <H1> — takie też mogą się zdarzyć, gdyż były prawidłowe w starszych wersjach języka — użyjemy opcji -i (skrót od Ignore letter case). Numery wierszy dodamy na wszelki wypadek, gdyby okazało się, że tytuł jest zapisany w więcej niż jednym wierszu pliku:

grep -i -n "<h[123456]" opis.html > spis_tresci

Podobnie jak w poprzednim przykładzie, wyszukiwana fraza została ujęta w cudzysłowy tekstowe. Oto przykładowa zawartość pliku wynikowego:

14:<h1>Efektywne wykorzystanie poleceń systemowych</h1>
26:<h2>Procesor poleceń</h2>
48:<h2>Wejście i wyjście standardowe</h2>
99:<h2>Filtry</h2>
134:<h3>Programy <code>more</code> i <code>less</code>: drukowanie na terminalu</h3>
151:<h3>Program <code>grep</code>: wyszukiwanie wzorca</h3>
166:<h4>Przykład</h4>
177:<h3>Program <code>sed</code>: edytor automatyczny</h3>
193:<h4>Przykład</h4>
269:<h3>Program <code>sort</code>: sortowanie danych w pliku</h3>
274:<h4>Przykład</h4>
293:<h4>Przykład</h4>
324:<h2>Automatyczne uruchamianie programu z różnymi argumentami: program <code>xargs</code></h2>
348:<h3>Przykład</h3>

Ostatni etap to usunięcie nadmiaru informacji (numery wierszy) i przeformatowanie (np. przekształcenie w listę i wykonanie odsyłaczy hipertekstowych) otrzymanego spisu.

Inne sposoby użycia

Na zakończenie warto wspomnieć o dwóch pożytecznych opcjach programu grep: opcja -c (ang. Count = policz) zamiast drukowania wierszy zawierających wzorzec, powoduje wydrukowanie jedynie ich liczby. Opcja -v (ang. reVert = odwróć) wymusza drukowanie tych wierszy, które nie zawierają poszukiwanego wzorca.

Uwaga! grep grep-owi nierówny; poszczególne wersje mogą nieco różnić się składnią. Może okazać się konieczne poprzedzenie wyszukiwanej frazy opcją -e (tzn. Expression = wyrażenie). Zamiast grep "wyrażenie" spróbuj wtedy grep -e "wyrażenie", pozostawiając resztę polecenia bez zmian.

W nowszych wersjach systemów Windows istnieje filtr findstr o funkcjonalności zbliżonej do grep. Działa on w oparciu o tę samą ideę co grep, choć różni się od niego składnią i możliwościami. Opis opcji programu findstr można wywołać korzystając z opcji /h.

Programy type i cat: wysyłanie zawartości plików na wyjście

Programy te robią rzecz bardzo prostą, lecz przydatną: przekazują na wyjście cały plik wejściowy lub wszystkie pliki wymienione w wierszu poleceń. W tym drugim przypadku plik wynikowy składa się z następujących po sobie zawartości wielu plików (operacja łączenia pionowego). Program type należy do systemu Windows. Program cat należy do systemów UNIX i GNU.

Dostępna jest instrukcja użytkowania programu cat w polskim tłumaczeniu.

Łączenie plików za pomocą operacji cat (lub type)
Pliki wejścioweWynik polecenia cat plik1 plik2 > plikwynikowy
plik1:
1	jeden	one
2	dwa	two
3	trzy	three
4	cztery	four
5	pięć	five 
plik2:
6	sześć	six
7	siedem	seven
8	osiem	eight
9	dziewięć	nine
0	zero	zero
plikwynikowy:
1	jeden	one
2	dwa	two
3	trzy	three
4	cztery	four
5	pięć	five 
6	sześć	six
7	siedem	seven
8	osiem	eight
9	dziewięć	nine
0	zero	zero

Program split: dzielenie pliku na części

Operacją odwrotną do łączenia jest jeden za drugim jest krajanie, czyli podział na kawałki (ang. split). W efekcie powstaje kilka plików zawierających kolejne odcinki pliku wejściowego. Operację tę realizuje program narzędziowy o nazwie split. Równoważny efekt da się osiągnąć, choć zwykle większym nakładem pracy operatora, korzystając z techniki „Zaznacz blok/Zapisz blok do pliku” w sesji edytora interaktywnego.

Sposób użytkowania programu split jest opisany w stosownej instrukcji.

Podział pliku na części za pomocą operacji split
Plik wejściowyWyniki polecenia split -l4 plik1 wynik
plik1:
1	jeden	one
2	dwa	two
3	trzy	three
4	cztery	four
5	pięć	five 
6	sześć	six
7	siedem	seven
8	osiem	eight
9	dziewięć	nine
0	zero	zero
wynikaa:
1	jeden	one
2	dwa	two
3	trzy	three
4	cztery	four
wynikab:
5	pięć	five 
6	sześć	six
7	siedem	seven
8	osiem	eight
wynikac:
9	dziewięć	nine
0	zero	zero

Programy head i tail: pobieranie wierszy początkowych i końcowych z plików

Programy te przekazują na wyjście ustaloną liczbę początkowych (head) lub końcowych (tail) wierszy pliku wejściowego. Ponieważ liczbę wierszy końcowych da się ustalić dopiero po pobraniu całego pliku, programu tail nie można uważać w ścisłym sensie za filtr. Domyślną liczbę wierszy (zazwyczaj 10) zmienia się za pomocą opcji wywołania, np. tail -25 dane.txt wydrukuje 25 końcowych wierszy pliku dane.txt. Użycie tail bywa bardzo wygodne nie tylko w trybie nieinteraktywnym, ale także podczas czytania plików danych, gdyż jest szybsze od uruchomienia edytora i przewinięcia długiego pliku do końca.

Dostępne są polskojęzyczne instrukcje użytkowania programów head i tail.

Pobieranie sekcji początkowych i końcowych z plików za pomocą operacji head i tail
Pliki wejścioweWynik polecenia
head -2 plik* > wyniki
Wynik polecenia
tail -2 plik* > wyniki
plik1:
1	jeden	one
2	dwa	two
3	trzy	three
4	cztery	four
5	pięć	five 
plik2:
6	sześć	six
7	siedem	seven
8	osiem	eight
9	dziewięć	nine
0	zero	zero
wyniki:
==> plik1 <==
1	jeden	one
2	dwa	two

==> plik2 <==
6	sześć	six
7	siedem	seven

wyniki:
==> plik1 <==
4	cztery	four
5	pięć	five 

==> plik2 <==
9	dziewięć	nine
0	zero	zero

Programy cut i paste: zarządzanie kolumnami plików znakowych

Cut (wytnij) i paste (wklej) to nazwy dwóch ważnych operacji edycyjnych. Pierwsza polega na pobraniu fragmentu pliku, a druga na wstawieniu przygotowanego uprzednio fragmentu w danym miejscu pliku. Operacje te, zaimplementowane w oparciu o schowek systemowy, są standardowymi funkcjami interaktywnych edytorów plików znakowych i dokumentów. Filtr cut i program paste (który filtrem nie jest, gdyż działa na dwóch wejściach) służą do nieinteraktywnego przeprowadzania tych operacji z poziomu tekstowego procesora poleceń.

Program cut czyta plik wejściowy i wysyła na standardowe wyjście fragmenty jego wierszy. Operator może sprecyzować, o jakie fragmenty pliku chodzi; zazwyczaj dotyczy to określonych kolumn znaków (czyli znaków zajmujących tę samą pozycję w kolejnych wierszach) lub kolumn pól danych (czyli słów rozdzielonych separatorami, zajmujących tę samą pozycję w kolejnych wierszach).

Dostępna jest instrukcja użytkowania programu cut w polskim tłumaczeniu.

Pobieranie kolumn z pliku za pomocą operacji cut
Plik wejściowyWynik polecenia
cut -f2 plik1 > plikwynikowy
Wynik polecenia
cut -c3-4 plik1 > plikwynikowy
plik1:
1	jeden	one
2	dwa	two
3	trzy	three
4	cztery	four
5	pięć	five 
6	sześć	six
7	siedem	seven
8	osiem	eight
9	dziewięć	nine
0	zero	zero
plikwynikowy:
jeden
dwa
trzy
cztery
pięć
sześć
siedem
osiem
dziewięć
zero
plikwynikowy:
je
dw
tr
cz
pi
sz
si
os
dz
ze

Program paste działa w pewnym sensie odwrotnie do cut. Generuje on plik, którego kolejne wiersze są wynikiem połączenia odpowiednich wierszy dwóch lub więcej wskazanych plików. Sposób łączenia (np. znak separujący części pochodzące z różnych plików) zadaje się jako opcję działania. Wyniki są wysyłane na wyjście standardowe.

Dostępna jest instrukcja użytkowania programu paste w polskim tłumaczeniu.

Łączenie kolumn za pomocą operacji paste
Pliki wejścioweWynik polecenia
paste plik1 plik2 > plikwynikowy
plik1:
jeden
dwa
trzy
cztery
pięć 
sześć
siedem
osiem
dziewięć
zero
plik2:
one
two
three
four
five 
six
seven
eight
nine
zero
plikwynikowy:
jeden	one
dwa	two
trzy	three
cztery	four
pięć	five 
sześć	six
siedem	seven
osiem	eight
dziewięć	nine
zero	zero

Uzupełnieniem kompletu cut/paste jest program join, którego działanie polega na wzajemnym dopasowaniu wierszy z dwóch plików wejściowych według zadanego wzorca. Dane wynikowe są umieszczane na wyjściu standardowym. Oba pliki muszą być uporządkowane według kolumn zawierających wzorzec. Operacja ta jest odpowiednikiem bazodanowej operacji join (złącz) i ma sens w odniesieniu do tabel baz danych przechowywanych w plikach znakowych.

Dostępna jest instrukcja użytkowania programu join w polskim tłumaczeniu.

Łączenie plików przez dopasowanie wierszy za pomocą operacji join
Pliki wejścioweWynik polecenia
join -1 1 plik1 -2 1 plik2 > plikwynikowy
Wynik polecenia
join -a 1 -a 2 -1 1 plik1 -2 1 plik2 > plikwynikowy
plik1:
0	zero
1	jeden
10	dziesięć
2	dwa
3	trzy
31	trzydzieści jeden
4	cztery
5	pięć 
6	sześć
7	siedem
8	osiem
9	dziewięć
plik2:
0	zero
1	one
11	eleven
12	twelve
2	two
3	three
4	four
5	five 
6	six
7	seven
8	eight
88	eighty eight
9	nine
plikwynikowy:
0 zero zero
1 jeden one
2 dwa two
3 trzy three
4 cztery four
5 pięć  five 
6 sześć six
7 siedem seven
8 osiem eight
9 dziewięć nine
plikwynikowy:
0 zero zero
1 jeden one
10 dziesięć
11 eleven
12 twelve
2 dwa two
3 trzy three
31 trzydzieści jeden
4 cztery four
5 pięć  five 
6 sześć six
7 siedem seven
8 osiem eight
88 eighty eight
9 dziewięć nine

Program sed: edytor automatyczny

Innym ważnym przykładem filtra jest edytor strumieniowy sed (ang. Stream EDitor), służący do automatycznego przekształcania zawartości pliku bez wizualnej kontroli operatora. Pojedyncze zmiany w pliku tekstowym moglibyśmy z powodzeniem przeprowadzać interaktywnie, na przykład przy użyciu funkcji wyszukiwania i zamiany edytora znakowego. Jeżeli jednak musimy zamienić kilkadziesiąt różnych fraz w kilkunastu plikach kilku kartotek (i na dodatek robić to regularnie raz w miesiącu), to edytor strumieniowy może się okazać niezastąpiony.

W przeciwieństwie do edytorów interaktywnych, edytor strumieniowy nie jest wyposażony w menu z przyciskami. Ma on jednak instrukcję użytkowania, którą należy się posługiwać.

Reguły przekształcania tekstu należy opracować przed uruchomieniem edytora i zapisać w pliku sterującym. Następnie można uruchomić edytor poleceniem postaci

sed -f plik-sterujący plik-wejściowy > plik-wynikowy

w którym plik-wejściowy zawiera tekst poddawany obróbce, a plik-wynikowy stanowi nazwę pliku, w którym ma być umieszczony efekt pracy. W żadnym wypadku nie powinien to być ten sam plik. Nazwę pliku wejściowego wolno pominąć, wtedy strumień danych będzie odczytywany ze standardowego wejścia.

Przykład: zastąpienie kropek dziesiętnych przecinkami dziesiętnymi

Znaki cyfr przed i po kropce trzeba wydrukować niezmienione. Oznaczymy je jako bloki parami komend-nawiasów \( i \). Blok numer n będzie nosił nazwę \n:

\([0-9]+\).\([0-9]+\) → \1,\2

czyli w składni poleceń programu sed

s/\([0-9]+\)\.\([0-9]+\)/\1,\2/g

Zastosowanie tej reguły zamiany nie wyrządzi krzywdy kropkom strzegącym końców zdań ani skrótom wyrazów.

Przykład: zmiana kolejności kolumn tabeli bazy danych zapisanej w pliku znakowym

Powiedzmy, że mamy do czynienia z plikiem zawierającym tabelę postaci

imię	nazwisko	data-urodzenia	wzrost

i że chcemy w nim zamienić kolejność kolumn pierwszej i drugiej. Elementy kolumn są oddzielane znakami tabulacji. Wpisy do tabeli mogą zawierać znaki liter wielkich i mały, znaki cyfr, znaki przestankowe (kreski poziome, ukośniki, kropki) i spacje. Dla uproszczenia przyjmiemy, że w tabeli używano tylko liter łacińskich. Zdefiniujemy wyrażenie regularne, w którym wyróżnimy następujące elementy:

Każdy prawidłowo zbudowany wiersz naszego pliku bazy danych jest wyrażeniem o podanej wyżej postaci. Należy go przekształcić do postaci:

nazwisko	imię	data-urodzenia	wzrost

którą możemy opisać za pomocą wyrażenia zastępującego

\2\t\1\t\3

Zatem odpowiedniej regule dla edytora strumieniowego można nadać postać:

s/\([a-zA-Z .\-]*\)\t\([a-zA-Z \.\-]*\)\t\([a-zA-Z0-9 \.,:;\/\?\t=\-]*\)$/\2\t\1\t\3/g

Oto gotowy plik reguł.

Przykład: poprawa interpunkcji w dokumencie znakowym

Przypomnijmy sobie podstawowe reguły automatycznej poprawy interpunkcji:

I już mamy gotowy plik reguł (przemyśl kolejność stosowania reguł; jest ona bardzo istotna). Działa? zobacz w takim razie, co się stanie z liczbą dziesiętną, taką jak 1.22 lub 1,234. Co proponujesz?

Przykład: hipertekstowy spis zawartości kartoteki

Dobrym przykładem poważnego zastosowania edytora strumieniowego jest wykonanie hipertekstowego spisu zawartości kartoteki, w którym nazwy wszystkich plików źródłowych HTML z danej kartoteki i jej podkartotek winny być objęte odsyłaczami do tych właśnie plików. Polecenie dir /s/b *.* > spis.txt (DOS/Windows) lub find * > spis.txt (UNIX) wydrukuje na wyjściu spis zawartości kartoteki wraz z podkartotekami i umieści go w pliku znakowym spis.txt. Pojedynczy wiersz tego pliku będzie miał postać

ścieżka\plik.roz

Zastosujmy edytor sed wydając polecenie

sed -f reguly spis.txt > spis.htm

które spowoduje, że program ten odczyta reguły zamiany z pliku reguly, zastosuje je do treści pliku spis.txt i zapisze zmodyfikowaną treść na wyjściu standardowym. Fragment wiersza poleceń > spis.htm przekieruje sygnał z wyjścia standardowego (terminala) do pliku spis.htm. Reguły zamiany określimy następująco: jeżeli kolejny wiersz pliku kończy się frazą .htm lub .html, to przekształć go do postaci <a href="wiersz">wiersz</a>. Inne wiersze kopiuj na wyjście bez zmian (nie będą miały odsyłaczy). Dla uproszczenia założymy, że ścieżka dostępu do pliku może zawierać wszystkie drukowalne znaki ASCII, to znaczy znaki o numerach od 32 (x20, znak spacji ) do 126 (x7e, znak tyldy ~). (Zauważmy, że zestaw ten zawiera oba ukośniki / i \ pełniące role łączników nazw w ścieżkach oraz dwukropek : stanowiący część nazwy stacji dysków.) Założymy też, że rozszerzenia nazw pisane są małymi literami. Na końcu (tylko w DOS/Windows) trzeba będzie zastąpić ukośniki \ ukośnikami /. W języku reguł programu sed wyrazić to można następująco:

s/[ -~]*\.html$/<a href="&">&<\/a>/g
s/[ -~]*\.htm$/<a href="&">&<\/a>/g
s/\\/\//g

lub może nieco mniej czytelnie (chociaż dla starszych wersji sed sposób ten jest bardziej odpowiedni)

s/[\x20-\x7e]*\.html$/<a href="&">&<\/a>/g
s/[\x20-\x7e]*\.htm$/<a href="&">&<\/a>/g
s/\\/\//g

Niestety, postać tych poleceń jest dość trudna do odczytania dla nieprzygotowanego użytkownika. O wiele łatwiej jest je konstruować, niż odczytywać.

Powstały plik trzeba uzupełnić o nagłówek i epilog języka HTML. Można to zrobić na przykład tak: najpierw umieścić w spisie pusty nagłówek o treści np. takiej:

<html><head>
<meta name="author" content="My we własnej osobie" />
<meta name="generator" content="Tak! sami umiemy generować takie dokumenty!" />
<title>Tytuł spisu</title>
</head><body>
<h1>Tytuł spisu</h1>
<pre>

Zrobimy to, kopiując nagłówek z wcześniej przygotowanego pliku o nazwie naglowek.html. Następnie dopiszemy do niego wydruk wygenerowany przez program sed, a całość zamkniemy epilogiem, przechowywanym w pliku nazwanym tu symbolicznie epilog.html. Jego treść stanowią polecenia zamykające:

</pre></body></html>

Czyli:

type nalgowek.html > spis.htm
sed -f reguly spis.txt >> spis.htm
type epilog.html >> spis.htm

I to już wszystko. Jeżeli w naszym przykładzie wiersza pliku spis.txt roz ma wartość htm, to odpowiedni wiersz na wyjściu będzie zawierać napis

<a href="ścieżka/plik.htm">ścieżka/plik.htm</a>

Oto odpowiedni plik reguł. Proszę sprawdzić, jak działa.

Przykład: poprawa postaci odsyłaczy do tekstów źródłowych

Niepoprawne napisy typu str. 35-56 oraz str. 35 - 56 w dokumencie HTML zamieniaj na poprawne str.&nbsp;35&ndash;56.

Przykład: zamiana strony kodowej, w jakiej zapisano dokument

Oto plik reguł.

Przykład: zamiana liter małych na wielkie

Zbuduj samodzielnie plik reguł.

Przykład: usuwanie poleceń formatowania HTML z pliku źródłowego

Należy usunąć ciągi znaków rozpoczynające się znakiem <, a kończące się znakiem >, lecz nie zawierające znaku > wewnątrz; zbuduj samodzielnie plik reguł. Co ze znacznikami, które zaczynają się w jednym wierszu, a kończą w innym? Co z symbolicznymi nazwami jednostek znakowych (jak &nbsp;)?

Przykład: nadawanie plikowi znakowemu postaci dokumentu HTML

Chcielibyśmy, żeby automat rozpoznał przynajmniej podstawowe elementy blokowej struktury dokumentu znakowego i wstawiał odpowiednie znaczniki HTML. Konieczne minimum to zastąpienie pustych wierszy znacznikami zakończenia poprzedniego akapitu i rozpoczęcia następnego oraz wstawienie „prologu” i „epilogu” dokumentu. Można też pomyśleć o traktowaniu niektórych akapitów (np. tych rozpoczynających się gwiazdką lub kreską poziomą) jako elementów listy. Ciągi znaków o ustalonym znaczeniu (jak np. --, ---, ,,, '', ..., &) powinny zostać zastąpione nazwami odpowiadających im jednostek znakowych. Przemyśl zagadnienie i zbuduj samodzielnie plik reguł realizujący wybraną część tego projektu.

Program tr: automatyczny translator znaków

W większości przykładów użycia edytora strumieniowego, włączonych do bieżącego opracowania, zadanie sprowadza się do dokonania ciągu zamian w przetwarzanym tekście. Nie jest to jedyny możliwy sposób użycia sed, lecz niewątpliwie jest najczęściej wykorzystywany i w związku z tym najważniejszy. W bieżącym podrozdziale zostanie omówione wyspecjalizowane narzędzie o nazwie tr (ang. TRansliterate). Jest ono przeznaczone do automatyzacji prostego, a jednocześnie istotnego etapu prac redakcyjnych nad plikami danych znakowych, polegającego na ustalonej zamianie pojedynczych znaków.

Zakres zastosowań tr jest ograniczony do przypadków, kiedy zamierzamy zastąpić znaki w całym pliku, bez względu na kontekst.

Program tr czyta plik wejściowy i buduje na jego podstawie plik wynikowy, w którym wszystkie wystąpienia znaków z pewnego zbioru zostaną zastąpione odpowiadającymi im znakami z innego zbioru. Zaawansowane opcje pozwalają ponadto usuwać wybrane powtórzenia znaków oraz usuwać wszystkie wystąpienia wskazanych znaków.

Programowi trzeba zatem przekazać opis dwóch ciągów znaków. Mimo że tr nie działa na wyrażeniach regularnych, tylko na pojedynczych znakach, to akceptuje w opisie ciągów znaków zastępowanych i zastępujących te elementy składni wyrażeń regularnych, które służą do definiowania zbiorów znaków.

Szczegółowe zasady używania programu tr opisuje jego instrukcja użytkowania.

Przykład: zamień w pliku danych separatory rekordów ze średników na końce wiersza

Zadanie to było rozważane przy okazji omawiania pracy z edytorami interaktywnymi. Znak nowego wiersza bywał opisywany dwojako: albo techniką kopiuj–wklej wzorzec, albo przez odwołanie do nazwy \n w notacji wyrażeń regularnych.

Chcąc użyć programu tr do wykonania polecenia:

w trakcie czytania pliku dane-wejsciowe 
zmieniaj napotkane średniki na znaki końca wiersza, 
a wynik zapisz w pliku plik-wynikowy

powinniśmy sformułować następujące polecenie systemowe:

tr ";" "\n" dane-wejsciowe > plik-wynikowy

Przykład: zamień w pliku danych separatory rekordów z końców wiersza na średniki

W tym przypadku użycie wyrażeń regularnych nie zawsze prowadzi do sukcesu. W wielu programach wyszukiwanie wzorca odbywało się zawsze w obrębie wiersza, zaś znak końca wiersza siłą rzeczy nie może znaleźć się we jego wnętrzu. Program tr czyta dane znak po znaku, zatem jest w stanie wyszukać i przetworzyć także znaki końca wiersza:

tr "\n" ";" dane-wejsciowe > plik-wynikowy

Przykład: zamiana liter małych na wielkie

Następujący sposób użycia tr spowoduje zamianę wszystkich małych liter łacińskich i polskich na odpowiadające im wielkie litery:

tr "a-ząćęłńóśźż" "A-ZĄĆĘŁŃÓŚŹŻ" dane-wejsciowe > plik-wynikowy

Przykład: zamiana cykliczna

W przeciwieństwie do rozpatrywanych wcześniej ciągów wielu zamian dokonywanych kolejno w obrębie całego pliku, tr operuje na bieżącym znaku, który natychmiast po przetworzeniu wysyła na wyjście. Nic więc nie stoi na przeszkodzie, by wystąpienia pewnego ustalonego znaku (powiedzmy +) zastąpić innym znakiem (powiedzmy -), a jednocześnie nakazać także zamianę w odwrotnym kierunku. Oto stosowne polecenie:

tr "\+\-" "\-\+" dane-wejsciowe > plik-wynikowy

(Odwrotne ukośniki są niezbędne, gdyż znaki + i - mają specjalne znaczenie w składni wyrażeń regularnych.)

W galerii ilustracji zamieszczone są zrzuty ekranowe z elementarnymi przypadkami użycia filtrów sed i tr.

Program sort: sortowanie danych w pliku

Program sort nie jest filtrem, ponieważ dane da się ostatecznie uporządkować dopiero po odczytaniu wszystkich wierszy z wejścia. Mimo to operuje on na standardowym pliku wejściowym i standardowym pliku wynikowym, a sposób jego użycia jest analogiczny do sposobu używania filtrów.

Dostępna instrukcja użytkowania programu sort dotyczy wersji polecenia z systemu GNU. Systemowe polecenie sort z systemu Windows ma nieco inne opcje.

Porządkowanie wierszy pliku za pomocą operacji sort
Plik wejściowy Wynik polecenia
sort plik1 > plikwynikowy
(sortowanie alfabetyczne według pierwszego pola — uwaga na długość ciągów znaków)
Wynik polecenia
sort -k1.1,1.2 plik1 > plikwynikowy
(sortowanie alfabetyczne według dwóch pierwszych znaków pierwszego pola)
Wynik polecenia
sort -n plik1 > plikwynikowy
(sortowanie numeryczne według pierwszego pola)
Wynik polecenia
sort -k2 plik1 > plikwynikowy
(sortowanie alfabetyczne według drugiego pola)
plik1:
1	jeden
2	dwa
4	cztery
3	trzy
31	trzydzieści jeden
5	pięć 
6	sześć
8	osiem
10	dziesięć
9	dziewięć
0	zero
7	siedem
plikwynikowy:
0	zero
10	dziesięć
1	jeden
2	dwa
31	trzydzieści jeden
3	trzy
4	cztery
5	pięć 
6	sześć
7	siedem
8	osiem
9	dziewięć
plikwynikowy:
0       zero
1       jeden
10      dziesięć
2       dwa
3       trzy
31      trzydzieści jeden
4       cztery
5       pięć
6       sześć
7       siedem
8       osiem
9       dziewięć
plikwynikowy:
0	zero
1	jeden
2	dwa
3	trzy
4	cztery
5	pięć 
6	sześć
7	siedem
8	osiem
9	dziewięć
10	dziesięć
31	trzydzieści jeden
plikwynikowy:
4       cztery
2       dwa
10      dziesięć
9       dziewięć
1       jeden
8       osiem
5       pięć
7       siedem
6       sześć
3       trzy
31      trzydzieści jeden
0       zero

Przykład: porządkowanie haseł słownika

Przeformatuj plik geometra.txt tak, by opis każdego pojęcia zawierał się w pojedynczym wierszu, tak jak tutaj:

Punkt jest czymś, co nie ma żadnej wielkości (Euklides). Punkt ma określone jedynie położenie.
Prosta posiada tylko jeden wymiar: długość (Euklides). Przez dwa różne punkty przechodzi dokładnie jedna prosta.
Odcinek jest fragmentem prostej, zawartym między dwoma punktami.
Łamana to linia utworzona z kolejnych odcinków (krawędzi, boków) tak, że koniec każdego odcinka jest jednocześnie początkiem następnego.
Bok łamanej to odcinek, będący częścią łamanej.
Łamana zamknięta to taka łamana, w której koniec ostatniego odcinka jest jednocześnie początkiem pierwszego.
Wielokąt to obszar na płaszczyźnie, ograniczony łamaną zamkniętą.
Bok wielokąta to bok łamanej, która go ogranicza.

Uporządkuj opisy alfabetycznie, wynik umieść w pliku slowniczek-geom.txt:

sort < geometra.txt > slowniczek-geom.txt

Usuwanie powtarzających się wyrazów z ciągu danych: program uniq

W wyniku kompletowania danych (np. drogą ankietowania, zbierania zamówień, łączenia list uczestników) powstają ciągi danych, w których ten sam element może występować wielokrotnie. W zapisie znakowym jednemu elementowi ciągu odpowiada jeden wiersz pliku znakowego. Posortowanie takiego pliku spowoduje zgrupowanie wszystkich elementów o tej samej wartości w bloku kolejnych wierszy. Program uniq (z ang. unique = jednoznaczny) odczytuje dane z wejścia standardowego, a na wyjściu standardowym zapisuje tylko pierwszy z każdego bloku identycznych wierszy. Jeżeli więc ciąg danych wejściowych jest uporządkowany, to plik wynikowy zawiera po jednym wierszu z opisem każdego elementu.

Dostępna jest instrukcja użytkowania programu uniq w polskim tłumaczeniu.

Usuwanie powtarzających się wierszy za pomocą operacji uniq
Plik wejściowyWynik polecenia uniq plik1 > plikwynikowy
plik1:
1	jeden	one
1	jeden	one
1	jeden	one
1	jeden	one
2	dwa	two
2	dwa	two
3	trzy	three
4	cztery	four
4	cztery	four
4	cztery	four
5	pięć	five 
6	sześć	six
7	siedem	seven
plikwynikowy:
1	jeden	one
2	dwa	two
3	trzy	three
4	cztery	four
5	pięć	five 
6	sześć	six
7	siedem	seven

Przykład wykorzystania filtrów

Grupa obserwatorów została skierowana do odczytania danych pomiarowych ze stacji terenowych. Obserwatorzy podzielili obszar na rejony, w których osobiście odczytywali pomiary. Jednak niektóre stacje położone w pobliżu granicy rejonów zostały sprawdzone także przez obserwatorów z sąsiedniego rejonu. Każdy z obserwatorów zapisał dane w pliku znakowym o nazwie postaci obserwacje.i i treści:

numer-stacji    data    wartość-odczytu

w którym separatorami danych są pojedyncze znaki tabulacji. Przed opracowaniem danych połączono wszystkie odczyty w jeden plik:

cat obserwacje.1 >> obserwacje
cat obserwacje.2 >> obserwacje
…
cat obserwacje.n >> obserwacje

(polecenie cat należy do systemu UNIX; w systemach DOS i Windows można skorzystać z polecenia type), następnie uporządkowano:

sort < obserwacje > obserwacje.up

i usunięto powtarzające się elementy:

uniq < obserwacje.up > obserwacje

W wyniku tych operacji otrzymano plik obserwacje, zawierający po jednym egzemplarzu każdego odczytu. W jednym z etapów pracy powstał plik pomocniczy obserwacje.up, który nie jest już potrzebny i można go usunąć.

Wykorzystanie mechanizmu potoku pozwala całą pracę zapisać w postaci jednego polecenia uruchamiającego trzy programy (cat, sort i uniq) odpowiedzialne za kolejne etapy przetwarzania:

cat obserwacje.* | sort | uniq > obserwacje

Technika ta ma dodatkową zaletę: danych pośrednich nie trzeba zapisywać w plikach tymczasowych.

Przedstawienie części wspólnej i różnic zawartości dwóch plików: program comm

Operacja łączenia tworzy plik będący sumą zawartości dwóch lub większej liczby danych plików. Uzupełnieniem wyznaczania sumy jest wyznaczanie różnic zawartości i części wspólnej zawartości dwóch plików. Jednym z narzędzi, które to umożliwiają, jest program o nazwie comm (z ang. common = wspólny). Czyta on wiersz po wierszu dwa pliki i wypisuje na wyjściu standardowym trzy kolumny: pierwsza zawiera tylko wiersze, których nie ma w drugim pliku; druga zawiera tylko wiersze, których nie ma w pierwszym pliku; trzecia zawiera wiersze obecne w obu plikach. Tworzenie każdej z tych kolumn można wyłączyć za pomocą odpowiedniej opcji. Podczas przetwarzania comm zakłada, że pliki wejściowe zostały posortowane wierszami; jeżeli tak nie jest, to wyniki są nieprzewidywalne.

Dostępna jest instrukcja użytkowania programu comm w polskim tłumaczeniu.

Znajdowanie różnic i części wspólnej uporządkowanych plików za pomocą operacji comm
Pliki wejścioweWynik polecenia comm plik1 plik2 > plikwynikowy
plik1:
cztery
dwa
dziewięć
jeden
osiem
sześć
trzy
plik2:
dwa
dziesięć
dziewięć
jeden
osiem
sześć
trzy
plikwynikowy:
cztery
		dwa
	dziesięć
		dziewięć
		jeden
		osiem
		sześć
		trzy

Automatyczne uruchamianie programu z różnymi argumentami: program xargs

Pojedynczy zestaw argumentów można przekazać programowi w wierszu poleceń. Program xargs (ang. eXecute with ARGumentS = wykonaj z argumentami) wykonuje wskazane polecenie wielokrotnie, za każdym razem przekazując mu kolejną porcję danych z wejścia standardowego jako listę argumentów.

start
  odczytaj wiersz poleceń;
  dopóki (nie doszedłeś do końca pliku standardowego wejścia) wykonuj (
    pobierz kolejną porcję danych z wejścia standardowego;
    wykonaj polecenie określone przez opcje wiersza poleceń z dołączonymi odczytanymi argumentami;
  );
koniec pracy.

Dobrze, ale cóż to jest „kolejna porcja danych”? Program xargs jest na tyle elastyczny, że pozwala użytkownikowi samemu sprecyzować to pojęcie. Może to być na przykład: jeden argument, dwa argumenty, wszystkie argumenty z jednego wiersza pliku wejściowego itp.

Dane na wejście standardowe mogą być podawane przez operatora bezpośrednio z konsoli: (xargs polecenie), odczytywane z pliku znakowego (przekierowanie wejścia standardowego: xargs polecenie < plik-argumentów) lub generowane przez jakiś program (praca potokowa: program | xargs polecenie).

Przykład: w każdej kartotece systemu plików umieść plik o nazwie spis, zawierający spis treści tej kartoteki

Pokażemy, jak poradzić sobie z tym zadaniem w środowisku systemu Windows. Polecenie dir > spis umieszcza w kartotece bieżącej plik spis zawierający wydruk polecenia dir *.*, czyli spis zawartości kartoteki.

Krok 1:
spis kartotek całego woluminu uzyskamy w systemie Windows poleceniem dir /b/s/ad \*.*.
Krok 2:
przygotowanie pliku z poleceniem wsadowym. Za pomocą edytora plików znakowych utworzymy plik piszkart.cmd o następującej zawartości:
dir %1 > %1\spis
Krok 3:
polecenie xargs --max-lines=1 piszkart.cmd spowoduje wykonanie kolejnych poleceń postaci
piszkart.cmd kolejny wiersz wejścia
Jeżeli na wejściu umieścimy listę kartotek:
dir /s/b/ad \*.* | xargs --max-lines=1 piszkart
to polecenie piszkart.cmd założy plik ze spisem w każdej kartotece, której nazwa wystąpi w listingu.
Krok 4:
poleceniem del piszkart.cmd usuwamy niepotrzebny już plik skryptowy (chyba że chcielibyśmy go zachować na lepsze czasy).

W opisie dla uproszczenia pominięto fakt, że nazwy kartotek w systemie Windows mogą zawierać spacje.

Pytania kontrolne

© Copyright 2000–2012 by Jan Jełowicki, Katedra Matematyki Uniwersytetu Przyrodniczego we Wrocławiu
janj@aqua.up.wroc.pl
http://karnet.up.wroc.pl/~jasj