Lab 03 - Filtry, strumienie oraz przetwarzanie potokowe

Filtry, strumienie oraz przetwarzanie potokowe

Standardowe wejście/wyjście

Każdy proces domyślnie korzysta ze standardowych strumieni danych, będących abstrakcją źródła lub ujścia danych. Dla każdego procesu system tworzy standardowy strumień wejściowy (ang. standard input), reprezentujący urządzenia wejściowe, np. klawiaturę i dysk, standardowy strumień wyjściowy (ang. standard output), którym może być terminal (monitor komputera) lub plik oraz standardowy strumień diagnostyczny (ang. standard error). Strumienie wejściowy, wyjściowy i diagnostyczny oznaczane są odpowiednio: stdin, stdout, stderr. Każdy strumień ma odpowiadające mu wartości: stdin - 0, stdout - 1 i stderr - 2.

Standardowe wejscie-wyjscie procesu

Standardowe strumienie procesów charakteryzują się następującymi cechami:

Działanie standardowych strumieni ilustruje program cat. Uruchomienie tego programu bez argumentów powoduje przepisanie tego, co zostanie wpisane z wejścia standardowego (klawiatury) na wyjście standardowe (okno terminala). Wpisywanie danych z klawiatury można zakończyć za pomocą kombinacji Ctrl-D (reprezentowane w listingach jako ^D).

Przekierowanie wejścia/wyjścia procesów

Istnieje możliwość przeadresowania strumieni wyjściowych i wejściowych. Zmianę standardowego wejścia, wyjścia i wyjścia diagnostycznego można dokonać za pomocą operatorów: >, <, >>, <<.

Operator > powoduje przeadresowanie standardowego wyjścia, czyli utworzenie pliku i zapisanie w nim tego, co proces wypisałby na standardowym wyjściu. Jeśli wskazany plik już istnieje, zostanie on usunięty i utworzony na nowo.

cat > plik.txt
To jest plik.
Ala ma kota.
^D

Operator < powoduje przeadresowanie standardowego wejścia procesu, czyli pobranie danych wejściowych ze wskazanego pliku:

cat < plik.txt
To jest plik.
Ala ma kota.

Operatory > i < można używać jednocześnie, przeadresowując zarówno wyjście jak i wejście, co spowoduje, że zawartość pliku plik.txt zostaje skopiowana do pliku plik_nowy.txt:

cat < plik.txt > plik_nowy.txt

Operator >> przeadresowuje standardowe wyjście, dopisując wyniki działania programu na końcu istniejącego pliku:

cat >> plik.txt
Kot ma Ale.
^D

Operator << powoduje, że do procesu zostaną przekazane dane ze standardowego wejścia aż do napotkania wskazanego napisu:

cat << przerwa
> Ala ma kota
> Kot ma Ale
> przerwa
Ala ma kota
Kot ma Ale.

Niektóre polecenia równolegle z wyświetlanymi na standardowym wyjściu informacjami wysyłają dodatkowe informacje informujące o błędach przetwarzania na standardowe wyjście diagnostyczne. Istnieje możliwość niezależnego przekierowania strumienia diagnostycznego, poprzez operator > poprzedzony numerem wyjścia diagnostycznego, czyli 2:

cat plik1.txt plik2.txt 2> plik3.err

Polecenie to spowoduje wyświetlenie zawartości plików plik1.txt i plik2.txt oraz zapisanie informacji o błędach do pliku plik3.err.

W celu pominięcia komunikatów o błędach, wyjście diagnostyczne można przeadresować do pliku /dev/null. Wszystko co zostaje wysłane do pliku null, znajdującego się w katalogu /dev zostanie utracone:

cat plik1.txt plik2.txt> plik3.txt 2> /dev/null

Polecenie to spowoduje zapisanie kolejno zawartości plików plik1.txt i plik2.txt do pliku plik3.txt oraz jednocześnie zignoruje komunikaty o błędach.

W przypadku gdy strumień diagnostyczny ma trafiać tam, gdzie strumień wyjściowy, należy użyć zapisu 2>&1:

cat plik1.txt plik2.txt> plik3.txt 2>&1

Wiele programów konsolowych działających na strumieniach może przyjąć również nazwę pliku jako wejście: polecenie cat < plik.txt wygeneruje taki sam efekt jak cat plik.txt, a w przypadku skomplikowanego potoku wersja druga może okazać się znacząco czytelniejsza. Dodatkowo, wiele programów akceptuje przekazanie - jako nazwy pliku, co w zależności od kontekstu oznacza wejście lub wyjście standardowe.

Przetwarzanie potokowe

Standardowe wyjście jednego procesu może być połączone ze standardowym wejściem innego procesu, tworząc tzw. potok pomiędzy tymi procesami.

Standardowe wejscie-wyjście procesu

Przetwarzanie potokowe polega na buforowaniu przez system danych produkowanych przez pierwszy proces i następnie odczytywaniu tych danych przez drugi proces. Innymi słowy proces w potoku czyta dane z wejścia, które zostało przeadresowane na wyjście procesu poprzedniego. W potoku może brać udział jednocześnie kilka procesów. Do przekierowania danych do kolejnego procesu używany jest znak |. Poniżej podano przykłady potoków:

Proces ls podaje wynik procesowi more, który w efekcie wyświetla listing strona po stronie:

ls -al | more

Proces who podaje wynik procesowi sort, podając posortowaną listę pracowników pracujących w systemie:

who | sort

Proces ps podaje wynik procesowi grep, wyszukując na liście procesów linii zawierających słowo csh:

ps -ef | grep csh

Proces ls podaje wynik procesowi sort, który następnie podaje wynik procesowi head, wyświetlając pierwszych 10 największych plików:

ls -l /usr/bin | sort -bnr +4 -5 | head

Filtry

Istnieją programy, których zadaniem jest odczyt danych ze standardowego wejścia, przetworzenie tych danych i ich zapis na standardowe wyjście. Programy takie nazywane są filtrami i są szeroko wykorzystywane w przetwarzaniu potokowym. Poniżej przedstawiono najczęściej wykorzystywane filtry:

Zasady konstrukcji podstawowych wyrażeń regularnych opisujących szukany wzorzec są następujące:

Domyślnie grep pracuje w uproszczonym trybie, gdzie znaki ?, +, {, }, |, ( oraz ) traktowane są dosłownie. Aby uzyskać zgodność z wyrażeniami regularnymi stosowanymi w innych językach (extended regular expressions), należy stosować grep z przełącznikiem -E lub używać polecenia egrep (które wewnętrznie wywołuje grep z przełącznikiem -E). Rozbudowuje to dostępne znaczniki między innymi o:

Jeśli chcemy, aby znak mający znaczenie specjalne w wyrażeniach regularnych (np. ., *, {, }, (, ), ^, [, ], \, <, >, $) został potraktowany dosłownie, musimy poprzedzić go backslashem (\). Ponadto, ponieważ wiele z wymienionych znaków ma w powłoce bash znaczenie specjalne, całe wyrażenie wpisane w konsoli należy umieścić w pojedynczym cudzysłowie ('), aby uniknąć ich interpretacji przez powłokę.

Przykłady potoków z wyrażeniami regularnymi:

Proste wyrażenie regularne - przeszukuje plik /etc/group pod kątem słowa student:

grep student /etc/group

Zaawansowane wyrażenia:

Wyświetla elementy znajdujące się w głównym katalogu, o nazwach zaczynających się na s i minimum trzech następujących dowolnych znakach:

ls / | egrep '^s.{3,}'

Listuje zawartość pliku /etc/hosts, sortuje ją, a następnie przeszukuje pod kątem wystąpienia adresu IP - liczby w formacie w.x.y.z, gdzie w, x, y oraz z to liczby o od 1 do 3 cyfr. Zwróć uwagę na backslash przed kropkami powodujący, że kropka jest traktowana dosłownie, a nie jako dowolny znak:

cat /etc/hosts | sort | egrep '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}'

Nowa linia na końcu pliku

Zwyczajowo w systemach uniksowych pliki tekstowe (konfiguracyjne, źródłowe, skrypty itp.) zakończone są znakiem nowej linii. Niektóre edytory (np. nano) same dodają znak nowej linii przy każdej edycji pliku.

Niektóre z przedstawionych poleceń mogą wymagać obecności znaku końca linii w ostatnim wierszu. W przypadku problemów z wczytywaniem ostatniego wiersza możesz to zweryfikować wyświetlając plik np. cat:

student@vbox-xubuntu-rtos:~$ cat zly_plik 
To jest linia tekstu
To jest kolejna liniastudent@vbox-xubuntu-rtos:~$ cat dobry_plik 
A tu jest zawartosc innego pliku
Zakonczonego znakiem nowej linii
student@vbox-xubuntu-rtos:~$ 

Zwróć uwagę, że po wyświetleniu pliku nie zakończonego znakiem nowej linii znak zachęty (student@vbox...) wyświetla się w tym samym wierszu, co zawartość pliku.

Zadania do samodzielnego wykonania

  1. Wyświetl plik /etc/passwd z podziałem na strony przyjmując, że strona ma 5 linii tekstu. Podpowiedź: sprawdź program more
  2. Stwórz pliki tekst1 oraz tekst2, wypełnij kilkoma linijkami tekstu. Korzystając z polecenia cat utwórz plik tekst3, który będzie składał się z zawartości plików tekst1 oraz tekst2.
  3. Wyświetl po 5 pierwszych linii wszystkich plików w swoim katalogu domowym w taki sposób, aby nie były wyświetlane ich nazwy. Podpowiedź: pamiętaj, że z programami, które przyjmują jako argumenty nazwy plików możesz używać wzorców.
  4. Wyświetl linie o numerach 3, 4 i 5 z pliku /etc/passwd
  5. Wyświetl linie o numerach 7, 6 i 5 licząc od końca pliku /etc/passwd (czyli kolejno 7. od końca, 6. od końca i 5. od końca)
  6. Wyświetl zawartość /etc/passwd w jednej linii
  7. Za pomocą filtru tr wykonaj modyfikację pliku, polegającą na umieszczeniu każdego słowa (oddzielonych spacją) w osobnej linii. Podpowiedź: aby przekazać znak spacji jako argument, musisz umieścić go w cudzysłowie
  8. Zlicz wszystkie pliki znajdujące się w katalogu /etc i jego podkatalogach
  9. Napisz polecenie zliczające sumę znaków z pierwszych trzech linii pliku /etc/passwd
  10. Wyświetl listę plików z aktualnego katalogu, zamieniając wszystkie małe litery na duże.
  11. Wyświetl listę praw dostępu do plików w aktualnym katalogu, ich rozmiar i nazwę
  12. Wyświetl listę plików w aktualnym katalogu, posortowaną według rozmiaru pliku
  13. Wyświetl zawartość pliku /etc/passwd posortowaną wg numerów UID w kolejności od największego do najmniejszego
  14. Wyświetl zawartość pliku /etc/passwd posortowaną najpierw wg numerów GID w kolejności od największego do najmniejszego, a następnie UID
  15. Podaj nazwy trzech najmniejszych plików w katalogu posortowane wg nazwy
  16. W pliku /etc/services przechowywana jest lista popularnych usług sieciowych, wraz z numerami portów i protokołem. Wylistuj (tylko) nazwy usług, które korzystają z protokołu UDP.
  17. Wyświetl, ile wirtualnych terminali (dev/tty) o numerach z zakresu 50-69 znajduje się w systemie.
  18. Zbuduj potok, który wyświetli w terminalu PID procesu cupsd.

Autorzy: Adam Bondyra, Jakub Tomczyński

Opracowano na podstawie materiałów projektu Otwartych Studiów Informatycznych (http://wazniak.mimuw.edu.pl/).