25 (25) DOC


Część piąta
Linux dla programistów

W tej części:


Rozdział 25.
gawk

Tim Parker


W tym rozdziale:

Język programowania o nazwie awk został opracowany przez trzech ludzi - byli to: Alfred Aho, Peter Weinberger i Brian Kernighan (nazwa tego języka pochodzi od pierwszych liter ich nazwisk). Program gawk jest implementacją języka awk rozprowadzaną zgodnie z warunkami licencji GNU.

gawk to coś więcej niż zwykły język programowania. Jest on praktycznie niezastąpionym narzędziem dla większości programistów i administratorów systemów. Sam język jest bardzo łatwy do opanowania, a jednocześnie zadziwiająco elastyczny. Gdy poznasz podstawowe zasady programowania w tym języku, będziesz zaskoczony, widząc, w jak wielu różnych sytuacjach może on znaleźć zastosowanie.

Aby pomóc Ci zrozumieć gawk, przedstawimy kolejno jego elementy, popierając teorię przykładami. Oczywiście najlepszym sposobem na zapoznanie się z językiem są samodzielne eksperymenty, do których gorąco zachęcamy. W tym rozdziale przedstawimy tylko podstawowe wiadomości dotyczące języka gawk, żywiąc nadzieję, że rozbudzi to Twoją ciekawość.

Ogólnie o języku gawk

gawk został zaprojektowany jako łatwy w użyciu język programowania, który umożliwia pracę z danymi zapisanymi w plikach (obejmuje to również dane przesyłane z innych programów bezpośrednio, za pomocą mechanizmu przekierowania lub mechanizmu potoków, nazywanych po angielsku pipe). Podstawowe możliwości tego języka to:

Pod wieloma względami język ten idealnie nadaje się na „pierwszy” język programowania, głównie z powodu prostych reguł i wszechstronnego zastosowania w codziennej pracy. Doświadczeni programiści z pewnością również docenią jego prostotę.

Pliki, rekordy i pola

Zwykle program napisany w języku gawk pracuje na podstawie o danych zapisanych w pliku. Często są to dane numeryczne, ale gawk radzi sobie również z danymi tekstowymi. Jeśli dane nie są zapisane w pliku, można przesłać je do programu, używając mechanizmu potoków lub innego mechanizmu przekierowania danych. Tylko pliki tekstowe w standardzie ASCII mogą być przetwarzane prawidłowo. Choć gawk umożliwia pracę z plikami binarnymi, rezultaty często okazują się nieprzewidywalne. Ponieważ jednak w systemie Linux większość informacji zapisywana jest w standardzie ASCII, nie stanowi to większego problemu.

Jako prosty przykład pliku, który może być obsłużony przez program napisany w języku gawk, rozpatrzmy plik zawierający książkę telefoniczną. Książka taka składa się z dużej liczby wpisów, ale wszystkie wpisy mają taki sam format: nazwisko, imię, adres, numer telefonu. Cała książka telefoniczna jest posortowana (najczęściej alfabetycznie według nazwisk), ale brak w niej zaawansowanych metod wyszukiwania.

Każdy wiersz takiego pliku jest kompletnym zestawem danych dotyczących jednego numeru telefonicznego i nazywany jest rekordem; przykładowo, rekordem jest wiersz zawierający dane na temat abonenta o nazwisku Jan Kowalski, włączając w to jego adres i numer telefonu.

Każda z informacji zawartych w rekordzie - na przykład imię, nazwisko czy numer telefonu - nazywana jest polem. W języku gawk polem jest każda pojedyncza informacja, natomiast rekordem - zestaw pól odpowiadający opisowi pojedynczej rzeczy. Zestaw rekordów nazywany jest plikiem.

W większości przypadków pola rozdzielane są jednym wybranym znakiem, na przykład spacją, średnikiem, przecinkiem czy znakiem tabulacji. Znak ten nazywany jest separatorem pól. Dobrym przykładem pliku o ściśle określonym formacie rekordów jest plik /etc/passwd, który może wyglądać na przykład tak:

tparker:t36s54hsh:501:101:Tim Parker:/home/tparker:/bin/bash
etreijs:2ys639dj3h:502:101:Ed Trejis:/home/etrejis:/bin/tcsh
ychow:1h27sj:503:101:Yvonne Chow:/home/ychow:/bin/bash

Jeśli przyjrzeć się temu plikowi, można zauważyć, że jako separator pól używany jest dwukropek. Każdy z rekordów (wierszy) zawiera siedem pól: identyfikator użytkownika, hasło (zakodowane), numer identyfikacyjny użytkownika, numer identyfikacyjny grupy, pole komentarza, ścieżkę dostępu do katalogu domowego i do domyślnego interpretera poleceń. Dwukropki używane są wyłącznie do rozdzielania poszczególnych pól. Program, który ma wyszukać szóste pole, musi jedynie znaleźć piąty dwukropek (ponieważ przed pierwszym polem nie ma dwukropka) - po nim na pewno nastąpi szóste pole.

Tu napotykamy pierwszy problem. Wróćmy ponownie do przykładu książki telefonicznej. Załóżmy, że zawartość pliku jest następująca:

Smith, John 13 Wilson St. 555-1283
Smith, John 2735 Artside Dr, Apt 123 555-2738
Smith, John 125 Westmount Cr 555-1728

My wiemy, że każdy z rekordów zawiera cztery pola: nazwisko, imię, adres i numer telefonu. gawk widzi taki plik jednak nieco inaczej. Ponieważ separatorem pól jest spacja, w pierwszym wierszu rozpoznaje on ciąg Smith jako pierwsze pole, ciąg John - jako drugie pole, 13 jako pole trzecie, Wilson - czwarte itd. Z punktu widzenia języka gawk pierwszy wiersz składa się z sześciu pól. Drugi wiersz ma natomiast osiem pól. Dodatkowe znaki białe (spacje i znaki tabulacji) są ignorowane, chyba że zmienisz separator pól na znak spacji lub tabulacji.

0x01 graphic

Podczas pracy z dowolnym językiem programowania musisz niestety postrzegać dane właśnie w taki sposób, jaki wynika z zasad działania tego języka. Komputery biorą wszystko dosłownie.

Aby można było używać pliku zawierającego książkę telefoniczną zgodnie z naszą koncepcją, najłatwiej jest nieco zmienić jego format, na przykład wstawiając jako separator znak \:

Smith/John/13 Wilson St./555-1283
Smith/John/2735 Artside Dr, Apt 123/555-2738
Smith/John/125 Westmount Cr/555-1728

gawk domyślnie traktuje znaki białe jako separatory pól, chyba że zostanie poinstruowany, by używać innego znaku. Jeśli pozostaniesz przy ustawieniu domyślnym, to nie ma znaczenia, ile spacji czy tabulatorów będzie występowało obok siebie - zostaną one potraktowane jako pojedynczy separator. Oczywiście istnieje sposób, by zmienić to zachowanie.

Kojarzenie wzorców i akcji

W języku gawk istnieje jeden format określony prawie dla wszystkich poleceń. Polecenie składa się z dwóch części: wzorca i odpowiadającej mu akcji. Za każdym razem, gdy uda się dopasować wzorzec do danych wejściowych, wykonywana jest skojarzona z nim akcja.

Podejście to jest nieco podobne do języka naturalnego. Załóżmy, że chcesz wytłumaczyć komuś, jak dojść na pocztę. Mógłbyś na przykład ująć to następująco: „na końcu ulicy skręć w prawo, potem idź aż do znaku stop, skręć w lewo, idź do końca ulicy i skręć w prawo”. Zapis takiej informacji zgodny z filozofią języka gawk mógłby wyglądać tak:

koniec ulicy: skręć w prawo
znak "stop": skręć w lewo
koniec ulicy: skręć w prawo

Po dopasowaniu wzorca podejmowane są odpowiednie akcje. Nie powinieneś skręcać w prawo, zanim nie dojdziesz do końca ulicy, nie powinieneś skręcać w lewo przed znakiem stop. Przykład jest może nieco uproszczony, ale oddaje ogólną ideę.

W języku gawk pary wzorzec-akcja podaje się w następujący sposób:

/wzorzec1/{akcja1}
/wzorzec2/{akcja2}
/wzorzec3/{akcja3}

Dzięki takiemu formatowi łatwo zorientować się, gdzie kończy się wzorzec, a zaczyna określenie skojarzonej z nim akcji. Każdy program w języku gawk jest zestawem takich par. Pamiętaj, że wzorce i akcje dotyczą danych tekstowych, więc wzorce są zwykle ciągami znaków, a akcje - poleceniami typu wyświetl czy usuń tekst.

W przypadku, gdy nie został podany żaden wzorzec, każdy tekst uważany jest za „pasujący” i odpowiednia akcja podejmowana jest za każdym razem. Jeśli nie podamy żadnej akcji, gawk skopiuje bez zmian wiersz pasujący do wzorca z wejścia na wyjście.

Spójrzmy na następujący przykład:

gawk '/tparker/' /etc/passwd

Polecenie to spowoduje wyszukanie wszystkich wierszy zawierających tekst tparker w pliku /etc/passwd, i, ponieważ nie określono żadnej akcji, wyświetlenie ich na ekranie. W tym przypadku gawk zachowuje się tak samo jak program grep.

Powyższy przykład pokazuje dwie ważne rzeczy: gawk może zostać uruchomiony z wiersza poleceń (jego parametrami są wtedy pary wzorzec-akcja i nazwa pliku z danymi wejściowymi), a pary wzorzec-akcja należy ująć w pojedynczy cudzysłów, aby mogły zostać odróżnione od nazwy pliku wejściowego.

Język gawk dopasowuje tekst do wzorca litera po literze, więc wzorzec „kot” zostanie odnaleziony zarówno w słowie „kot”, jak i w słowie „maskotka”. Jeśli chcesz wyszukać tylko wiersze zawierające cały wyraz, powinieneś otoczyć wzorzec spacjami (” kot ”). Ważna jest również wielkość liter. Na szczęście w języku gawk dostępnych jest wiele znaków specjalnych rozszerzających lub zawężających zakres poszukiwań; zostaną one omówione w sekcji „Symbole specjalne”.

Wybiegając nieco naprzód, przeanalizujmy następujące polecenie:

gawk '{print $3}' plik2.dat

Spowoduje ono wyświetlenie (akcja określona jest przez polecenie print - wyświetl) trzeciego pola ($3) każdego wiersza pliku plik2.dat (ponieważ nie podano żadnego wzorca). Domyślnym separatorem pól jest spacja, jeśli więc spróbujemy użyć tego polecenia, podając jako plik wejściowy plik /etc/passwd, najpewniej nie zostanie wyświetlony żaden tekst, ponieważ tam separatorem pól jest dwukropek.

Możemy połączyć dwa podane wcześniej przykłady:

gawk '/UNIX/{print $2}' plik2.dat

Powyższe polecenie spowoduje przeszukanie pliku plik2.dat i wyświetlenie drugiego pola każdego wiersza zawierającego wyraz UNIX.

0x01 graphic

Cudzysłów otaczający parę wzorzec-akcja jest bardzo ważny i nie powinien być pomijany. Bez niego polecenie może nie zostać prawidłowo wykonane. Upewnij się również, że używasz właściwych znaków cudzysłowu (a nie na przykład pojedynczego cudzysłowu na początku, a podwójnego na końcu).

W obrębie jednego polecenia można oczywiście zdefiniować więcej par wzorzec-akcja, np.:

gawk '/skandal/{print $1} /rozwod/{print $2}' plotki.txt

Powyższe polecenie spowoduje wyszukanie w pliku plotki.txt wszystkich wierszy zawierających słowo skandal i wyświetlenie ich pierwszego pola, a następnie ponowne przeszukanie tego pliku od początku, tym razem wyświetlając drugie pole wierszy zawierających słowo rozwod. Przeszukiwanie dla każdej pary wzorzec-akcja rozpoczyna się od początku pliku.

Proste wzorce

Jak już się pewno zorientowałeś, gawk numeruje poszczególne pola rekordu: pierwsze pole to $1, drugie - $2 itd. Cały rekord nazywa się $0. Dla uproszczenia gawk pozwala opuścić argument $0 w nieskomplikowanych instrukcjach, tak więc wszystkie poniższe polecenia dadzą ten sam rezultat:

gawk '/tparker/{print $0}' /etc/passwd
gawk '/tparker/{print}' /etc/passwd
gawk '/tparker/' /etc/passwd

Oczywiście gawk potrafi o wiele więcej niż tylko wyszukać i wydrukować fragment tekstu. Można na przykład porównać zawartość danego pola ze stałą:

gawk '$2 == "cos" {print $3}' plik_testowy

Powyższe polecenie nakazuje porównanie drugie pola każdego rekordu zapisanego w pliku plik_testowy ze stałą "cos", a w przypadku, gdy wynik porównania będzie pozytywny, wydrukowanie trzeciego pola opracowywanego rekordu.

W tym przykładzie zauważyć można kilka ważnych szczegółów. Po pierwsze, wzorzec nie jest otoczony znakami /, ponieważ nie chcemy porównywać danych z konkretnym tekstem, ale sprawdzić pewien warunek. Po drugie, symbol == jest operatorem porównania. Należy używać podwójnego znaku równości, ponieważ pojedynczy znak =, jak się wkrótce przekonasz, służy do przypisywania wartości zmiennej. Podwójny cudzysłów otaczający tekst cos zapobiega jego interpretacji. Stałych numerycznych nie trzeba otaczać cudzysłowem.

0x01 graphic

Nie pomyl cudzysłowu używanego dla wymuszenia "dosłownej" interpretacji ciągu znaków z tym otaczającym parę wzorzec-akcja w wierszu poleceń. Jeśli w obu przypadkach użyjesz tego samego rodzaju cudzysłowów, gawk nie będzie w stanie prawidłowo zinterpretować polecenia.

Porównania i arytmetyka

Jedną z podstawowych cech każdego języka programowania jest możliwość porównania dwóch tekstów lub liczb i ustalenia, czy są one identyczne. W języku gawk dostępnych jest kilka operatorów pozwalających na porównywanie argumentów (między innymi wspomniany wcześniej operator ==) - zostały one zebrane w tabeli 25.1.

Tabela 25.1. Operatory porównania

Symbol

Opis

a==b

a jest równe b

a!=b

a nie jest równe b

a>b

a jest większe od b

a<b

a jest mniejsze od b

a>=b

a jest większe lub równe b

a<=b

a jest mniejsze lub równe b

Operatory te prawdopodobnie są Ci dobrze znane (identyczne lub bardzo podobne występują w każdym języku programowania). Jako przykład ich zastosowania niech posłuży poniższe polecenie, wyświetlające każdy wiersz pliku test, w którym wartość w czwartej kolumnie jest większa od 100:

gawk '$4 > 100' test

W języku gawk dostępne są również wszystkie podstawowe operatory arytmetyczne (dodawanie, odejmowanie, mnożenie i dzielenie), jak również kilka bardziej zaawansowanych (np. funkcje wykładnicze czy reszta z dzielenia). Przedstawia je tabela 25.2.

Tabela 25.2. Operatory arytmetyczne

Operator

Znaczenie

Przykład

+

Dodawanie

2+6

-

Odejmowanie

6-2

*

Mnożenie

2*5

/

Dzielenie

8/3

^

Potęga

3^2 (=9)

%

Reszta z dzielenia

9%4 (=1)

Można również używać jednocześnie operatorów arytmetycznych i numerów pól, np. polecenie

{print $3/2}

powoduje wyświetlenie wartości zawartych w trzeciej kolumnie podzielonych przez dwa.

Do dyspozycji mamy też zestaw funkcji trygonometrycznych oraz generujących liczby przypadkowe; zebrano je w tabeli 25.3.

Tabela 25.3. Funkcje matematyczne

Funkcja

Opis

sqrt(x)

Pierwiastek kwadratowy z x

sin(x)

Sinus x (x w radianach)

cos(x)

Kosinus x (x w radianach)

atan2(x,y)

Arkus tangens x/y

log(x)

Logarytm naturalny z x

exp(x)

Stała e do potęgi x

int(x)

Część całkowita z x

rand()

Liczba przypadkowa z zakresu 0 - 1

srand(x)

Przypisanie zarodkowi generatora liczb losowych wartości x

Kolejność wykonywania operacji jest taka sama jak w normalnej arytmetyce: najpierw obliczane są wyrażenia w nawiasach, potem potęgowania, następnie mnożenia, dzielenia i obliczanie reszty, na koniec dodawania i odejmowania, na przykład:

{print $1+$2*$3}

powoduje wyświetlenie iloczynu wartości w kolumnach drugiej i trzeciej powiększonego o wartość z kolumny pierwszej. Dla wymuszenia innej kolejności wykonywania działań należy użyć nawiasów; polecenie

{print ($1+$2)*$3}

powoduje wyświetlenie iloczynu wartości w polu trzecim i sumy wartości z pól pierwszego i drugiego. Jeśli masz wątpliwości co do tego, czy działania zostaną wykonane w żądanej kolejności, zawsze powinieneś używać nawiasów.

Łańcuchy znaków i liczby

Jeśli znasz już jakiś język programowania, zagadnienia te nie są dla Ciebie niczym nowym. Jeśli nie, nie przejmuj się, ponieważ są one bardzo proste. Mimo tego jednak zadziwiająco wiele osób plącze się beznadziejnie przy programowaniu tylko dlatego, że użyli łańcucha znaków tam, gdzie powinni byli użyć liczby.

Łańcuch znaków powinien zawsze być otoczony cudzysłowem. Liczby nie są nim otaczane i traktowane są jako liczby rzeczywiste. Polecenie

gawk '$1!="Tim" {print}' test

spowoduje wyświetlenie wierszy pliku test nie zaczynających się od tekstu Tim. Gdybyśmy opuścili cudzysłów, polecenie nie zostałoby zinterpretowane poprawnie. Natomiast polecenie

gawk '$1=="50" {print}' test

spowoduje wyświetlenie wierszy, w których pierwsza kolumna zawiera tekst 50; nie zostanie przeprowadzone porównanie wartości zapisanej w pierwszej kolumnie z liczbą 50, ale porównanie znak po znaku. Tekst "50" nie oznacza w języku gawk tego samego, co liczba 50.

Formatowanie wyjścia

Widziałeś już, w jaki sposób można wykonywać proste operacje. Można również podejmować akcje nieco bardziej skomplikowane, np.:

gawk '$1 != "Tim" {print $1, $5, $6, $2}' test

Powyższe polecenie wyświetla pierwszą, piątą, szóstą i drugą kolumnę każdego wiersza, w którego pierwszym polu znajduje się tekst inny niż Tim. Polecenie print może wyświetlić dowolną liczbę pól.

Można również za pomocą polecenia print wyświetlać stałe teksty, np.

gawk '$1 != "Tim" {print "Pole ", $1 " nie zawiera tekstu Tim"}' test

Poszczególne elementy polecenia print rozdzielane są przecinkami. Na końcach tekstów stałych, które mają zostać wydrukowane, wstawiono spacje, które będą rozdzielały tekst i wyświetlaną po nim wartość.

Dostępne są także inne funkcje formatujące, zaczerpnięte z języka C, np. printf (ang. print formatted). Funkcja ta używa łańcucha formatującego wyświetlany tekst oraz ciągu zmiennych odpowiadających każdemu odwołaniu w łańcuchu, na przykład tak:

{printf "%7s lubi ten jezyk\n", $2}

%7s to część łańcucha formatującego, powodująca wyświetlenie siedmiu znaków zmiennej, której identyfikator podano po tym łańcuchu (czyli drugiego pola). Symbol \n na końcu łańcucha formatującego powoduje przejście do następnego wiersza. Jeśli druga kolumna czterowierszowego pliku zawiera imiona, podane wyżej polecenie printf spowoduje, że tekst zostanie sformatowany w następujący sposób:

Lech lubi ten jezyk
Jacek lubi ten jezyk
Wojtek lubi ten jezyk
Agniesz lubi ten jezyk

Symbol formatu %7s powoduje wyrównanie wyświetlanych danych do prawej krawędzi pola, co zapobiega występowaniu nadmiarowych spacji w środku zdania.

Tabela 25.4 zawiera znaki specjalne, które mogą wystąpić po symbolu % w łańcuchu formatującym; znaki te decydują, jakiego typu zmienna ma zostać odczytana i podstawiona z listy zmiennych następującej po tym łańcuchu.

Tabela 25.4. Znaki specjalne łańcucha formatującego

Znak

Opis

c

Pierwszy znak łańcucha znaków lub znak o kodzie odpowiadającym wartości liczby całkowitej

d

Liczba całkowita

e

Liczba rzeczywista w notacji wykładniczej

f

Liczba rzeczywista w notacji tradycyjnej

g

Liczba rzeczywista w notacji wykładniczej lub tradycyjnej,
w zależności od tego, która z nich jest krótsza

o

Liczba ósemkowa bez znaku

s

Łańcuch znaków

x

Liczba szesnastkowa bez znaku

Przed każdym z tych symboli można podać liczbę określającą szerokość pola, w którym dana zostanie wyświetlona, na przykład symbol %6d oznacza, że dana całkowita ma być wyświetlana w polu o szerokości sześciu znaków. Można również użyć znaku - (minus), który wymusi wyrównywanie do lewej strony, zamiast domyślnie do prawej. Analogicznie do poprzedniego przykładu wydanie polecenia

gawk {printf "%-7s lubi ten jezyk\n", $2} imiona

da następujący rezultat:

Lech lubi ten jezyk
Jacek lubi ten jezyk
Wojtek lubi ten jezyk
Agnieszlubi ten jezyk

Można użyć kilku rodzajów zmiennych jednocześnie, pamiętając o tym, że każdemu odwołaniu do zmiennej w łańcuchu formatującym musi odpowiadać dokładnie jedna zmienna:

{printf "%7s pracuje przez %2d godzin dziennie i zarabia %6fzl *miesiecznie", $1, $2, $3}

Przy wyświetlaniu liczb można określić ich precyzję używając kropki, po której następuje liczba cyfr po przecinku, które mają być wyświetlane:

{printf "%7s zarabia %.2fzl na godzine.", $1, $6}

Powyższe polecenie formatuje tekst w ten sposób, że dane zawarte w pierwszej kolumnie wyświetlane są w polu o szerokości siedmiu znaków, natomiast dane numeryczne z kolumny szóstej - z dokładnością do dwóch miejsc po przecinku, na przykład tak:

Piotr zarabia 9.12zl na godzine.
Michal zarabia 12.10zl na godzine.

Jeśli chcesz ograniczyć liczbę cyfr wyświetlanych na prawo od kropki, możesz to zrobić następująco:

{printf "%7s zarabia %6.2fzl na godzine.", $1, $6}

Powyższe polecenie spowoduje, że wyświetlonych zostanie sześć cyfr do kropki i dwie cyfry po kropce dziesiętnej.

Symbole rozpoczynające się od znaku lewego ukośnika \ (ang. escape codes) mają również specjalne znaczenie (jeden z nich już poznałeś - \n powoduje przejście do nowego wiersza). Zebrano je w tabeli 25.5.

Tabela 25.5. Symbole zaczynające się od znaku \

Symbol

Opis

\a

Sygnał dźwiękowy

\b

Usunięcie znak na lewo od kursora

\f

Wysunięcie papieru

\n

Nowy wiersz

\r

Powrót karetki

\t

Tabulator

\v

Tabulator pionowy

\ooo

Symbol o kodzie ósemkowym ooo

\xdd

Symbol o kodzie szesnastkowym dd

\znak

Dowolny znak, bez interpretowania przez gawk

Za pomocą tych symboli można uzyskać wydrukowanie znaku mającego dla języka gawk specjalne znaczenie - na przykład znaku " - przez poprzedzenie go lewym ukośnikiem:

{printf "Powiedziałem \"Czesc\" a on odpowiedzial \"Witaj\"."}

Takie użycie znaku lewego ukośnika wygląda może nieco dziwnie, ale jest konieczne dla uniknięcia problemów. W tym rozdziale pojawi się więcej przykładów użycia tego typu sekwencji znaków.

Zmiana separatora pól

Jak wspomniano wcześniej, domyślnymi separatorami pól są znaki białe (spacje i znaki tabulacji). Nie zawsze jest to wygodne, o czym przekonaliśmy się, próbując przetwarzać plik /etc/passwd. Do zmiany separatora pól służy podawana z wiersza poleceń opcja -F, po której następuje nowy separator, np.

gawk -F":" '/tparker/{print}' /etc/passwd

Powyższe polecenie zmienia separator pól na dwukropek i szuka w pliku /etc/passwd wierszy zawierających tekst tparker. Nowy separator pól należy ująć w podwójny cudzysłów. Opcja -F (koniecznie pisana wielką literą) musi znaleźć się przed określeniem par wzorzec-akcja.

Symbole specjalne

W skład wzorców w języku gawk mogą również wchodzić symbole wieloznaczne i inne znaki specjalne. Z podobną sytuacją mieliśmy już do czynienia w przypadku interpreterów poleceń. Przykładowo, wzorzec kot pasuje do każdego wiersza zawierającej te trzy litery, jedna po drugiej. Jeśli chcesz być bardziej precyzyjny i znaleźć tylko wystąpienia słowa kot, a nie na przykład maskotka czy Szkot, możesz otoczyć to słowo spacjami:

/ kot /{print}

A co w sytuacji? jeśli chcesz znaleźć to słowo bez względu na to, czy jest pisane małymi, czy wielkimi literami? Można w takim przypadku użyć symbolu |, który oznacza „lub”:

/ kot | KOT /{print}

W ten sposób nadal jednak nie uda się odnaleźć słowa Kot. Można użyć możliwości oferowanych przez operator [], który pozwala na zdefiniowanie dowolnego zbioru akceptowanych znaków. Aby znaleźć słowo kot pisane małymi lub wielkimi literami w dowolnych kombinacjach, można wpisać:

/ [Kk][Oo][Tt] /{print}

Wygląda to dość dziwnie; na szczęście takie konstrukcje raczej nie przydają się w praktyce. Aby znaleźć tylko wyrazy kot i Kot, wystarczy wpisać

/ [Kk]ot /{print}

Przydatnym znakiem specjalnym jest tylda (~). Jest ona używana po to, by wskazać, w którym polu konkretnie ma być szukany zadany tekst, np. wzorzec:

$5 ~/tparker/

pasuje do każdego wiersza, w którym piąte pole zawiera tekst tparker. Działanie tego operatora jest podobne do operatora ==. Operator ~ można również zaprzeczyć:

$5 !~/tparker/

Powyższy wzorzec pasuje tylko do tych wierszy, których piąte pole jest różne od tekstu tparker.

Istnieje jeszcze kilka innych znaków specjalnych języka gawk. W większości mają one funkcje podobne do tych używanych w powłokach, więc nie powinieneś mieć problemów z ich stosowaniem. Zebrano je w tabeli 25.6.

Tabela 25.6. Znaki specjalne języka gawk

Znak

Znaczenie

Przykład

Opis przykładu

^

Początek pola

$3~/^b/

Pasuje, gdy trzecie pole zaczyna się od litery b

$

Koniec pola

$3~/$b/

Pasuje, gdy trzecie pole kończy się literą b

.

Dowolny znak

$3~/i.m/

Pasuje, gdy w trzecim polu występuje litera i, po niej dowolna jedna litera,
a następnie m

|

Lub

/kot|KOT/

Pasuje o tekstu kot lub KOT

*

Zero lub więcej powtórzeń znaku

/UNI*X/

Pasuje do słów UNX, UNIX, UNIIX, UNIIIX itd.

+

Jedno lub więcej powtórzenie znaku

/UNI+X/

Pasuje do słów UNIX, UNIIX itd.,
ale nie do UNX

\{a,b\}

Ilość powtórzeń pomiędzy a i b

/UNI\{1,3\}X

Pasuje tylko do tekstów UNIX, UNIIX i UNIIIX

?

Zero lub jedno powtórzenie

/UNI?X/

Pasuje tylko do UNX i UNIX

[]

Zestaw znaków

/I[BDG]M/

Pasuje do IBM, IDM i IGM

[^]

Znaki spoza
zestawu

/I[^DE]M/

Pasuje do wszystkich ciągów trzyznakowych zaczynających się od I a kończących na M, za wyjątkiem IDM i IEM

Niektóre z tych symboli używane są dość często. Przykłady ich użycia zawarte są w dalszej części tego rozdziału.

Wywoływanie programów w języku gawk

Wywoływanie programu gawk z pojedynczą parą wzorzec-polecenie nie jest w przypadku bardziej skomplikowanych zadań ani wygodne, ani szybkie. Warto wtedy zapisać polecenia języka gawk do pliku ASCII (zwanego dalej skryptem). Oto przykładowa zawartość skryptu gawk:

/tparker/{print $6}
$2!="cos"{print}

Pierwsze polecenie powoduje wyszukanie tekstu tparker i wyświetlenie szóstego pola zawierających go wierszy. Drugie polecenie skryptu powoduje wznowienie szukania od początku pliku, wyświetlając wszystkie wiersze, których drugie pole nie zawiera tekstu cos. W skrypcie nie trzeba otaczać par wzorzec-akcja pojedynczym cudzysłowem, tak jak miało to miejsce w przypadku podawania ich w wierszu poleceń. Po zapisaniu takiego skryptu pod nazwą np. skrypt1, można uruchomić go, wydając polecenie

gawk -f skrypt1 plik_we

Polecenie to spowoduje przetworzenie wszystkich par wzorzec-akcja zapisanych w skrypcie, traktując jako dane wejściowe plik o nazwie plik_we. W ten sposób działa większość programów napisanych w języku gawk. Uważaj, by nie pomylić opcji -f i -F, ponieważ mają one zupełnie inne znaczenia.

Jeśli chcesz użyć innego separatora pól, opcja -F musi wystąpić po opcji -f (oczywiście można również zapisać odpowiednie polecenie w pliku skryptu, ale o tym za chwilę):

gawk -f skrypt1 -F ":" plik_we

Można też przetwarzać więcej niż jeden plik, dopisując na końcu polecenia kolejne ich nazwy:

gawk -f skrypt1 plik1 plik2 plik3 ...

Domyślnie dane wyjściowe programu gawk kierowane są na ekran. Możesz oczywiście przekierować je do pliku:

gawk -f skrypt1 plik_we > wynik

Można również podać nazwę pliku wyjściowego wewnątrz skryptu, o czym będzie mowa później.

BEGIN oraz END

Podczas pisania skryptów często przydatne okazują się dwa specjalne wzorce obsługiwane przez gawk: BEGIN oraz END. BEGIN używany jest do podejmowania pewnych działań przed rozpoczęciem przetwarzania pliku, zwykle do inicjalizacji jakichś wartości, ustawienia separatora pól itp. END pozwala na wykonanie działań po zakończeniu przetwarzania pliku, zwykle wyświetlenia podsumowania lub po prostu informacji o zakończeniu działania.

Instrukcje następujące po wzorcach BEGIN i END należy otoczyć nawiasami klamrowymi. Wzorce te muszą być pisane wielkimi literami. Poniżej przedstawiamy prosty przykład ich zastosowania:

BEGIN {print "Rozpoczynam przetwarzanie pliku"}
$1 == "UNIX" {print}
$2 >10 {printf "Ten wiersz zawiera wartosc %d", $2}
END {print "Koniec przetwarzania pliku"}

W powyższym skrypcie najpierw wyświetlana jest wiadomość Rozpoczynam przetwa­rza­nie pliku, a następnie wszystkie wiersze pliku wejściowego, które w pierwszej kolumnie zawierają wyraz UNIX. Następnie plik jest przetwarzany ponownie i jeśli drugie pole wiersza zawiera wartość liczbową większą od 10, wartość ta jest wyświetlana wraz z odpowiednim komunikatem. Na koniec wyświetlana jest informacja "Koniec prze­twa­rzania pliku".

Zmienne

Wiesz na pewno, że zmienna to miejsce, w którym zapisywane mogą być pewne wartości. Z każdą zmienną w języku gawk skojarzona jest odpowiadająca jej nazwa. Wartość zmiennej może oczywiście się zmieniać.

Do przypisania wartości zmiennej służy operator =, na przykład

zm1 = 10

powoduje przypisanie zmiennej zm1 wartości 10 (jest to liczba, nie tekst). W języku gawk, w przeciwieństwie do wielu innych języków programowania nie deklaruje się zmiennych przed ich użyciem. Dzięki temu używanie zmiennych jest bardzo proste.

0x01 graphic

Operatory = (operator przypisania) i == (operator porównania) są często mylone. Trzeba uważać przy ich stosowaniu.

Zmiennych można używać wewnątrz nawiasów klamrowych określających akcje:

$1 == "Plastik" {licznik=licznik+1}

Ta para wzorzec-akcja sprawdza, czy pierwsze pole rekordu zawiera tekst Plastik, jeśli tak, to zwiększa o jeden zmienną licznik. Gdzieś przed tym wierszem powinniśmy przypisać zmiennej licznik wartość początkową (zwykle w sekcji BEGIN), w przeciwnym przypadku może się okazać, że dodajemy jeden do jakiejś zupełnie przypadkowej wartości.

0x01 graphic

W zasadzie gawk inicjalizuje wszystkie zmienne wartością zero, więc ręczna inicjalizacja nie jest tak naprawdę niezbędna, ale taka praktyka jest dobrym zwyczajem i warto ją stosować, ponieważ jest wymagana w niektórych innych językach programowania.

Oto trochę bardziej praktyczny przykład:

BEGIN { licznik=0 }
$5=="UNIX" {licznik=licznik+1}
END {printf "Znaleziono %d wystapien slowa UNIX",licznik}

W sekcji BEGIN zmiennej licznik nadawana jest wartość 0. Następnie przetwarzany jest plik wejściowy, a każde wystąpienie tekstu UNIX w piątym polu opracowywanego rekordu powoduje zwiększenie o jeden wartości zmiennej licznik. Po zakończeniu przetwarzania pliku wyświetlona zostanie wartość tej zmiennej.

Zmienne mogą być używane zarówno w odniesieniu do pól, jak i wartości, dopuszczalne są więc konstrukcje takie jak:

licznik=licznik+$6
licznik=$5-8
licznik=$5+zm1

Zmienne mogą również być częścią wzorca:

$2 > max_wart {print "Wartosc maksymalna przekroczona o ", $2-max_wart}
$4 - zm1 <min_wart {print "Nieprawidlowa wartosc :", $4}

Dostępne są również dwa specjalne operatory służące do zwiększania (inkrementacji) i zmniejszania (dekrementacji) wartości zmiennej o 1. Zostały one zapożyczone z języka C:

licznik++ zwiększenie wartości zmiennej licznik o 1

licznik-- zmniejszenie wartości zmiennej licznik o 1.

Zmienne wewnętrzne

Język gawk posiada również pewną ilość zmiennych wewnętrznych (ang. built-in), zawierających takie informacje, jak liczba przetworzonych rekordów itp. Są one przydatne głównie przy wyświetlaniu różnego rodzaju podsumowań. Ważniejsze zmienne wewnętrzne zostały zebrane w tabeli 25.7.

Tabela 25.7. Ważniejsze zmienne wewnętrzne języka gawk

Zmienna

Opis

NR

Liczba przeczytanych rekordów

FNR

Liczba rekordów przeczytanych z bieżącego pliku

FILENAME

Nazwa aktualnego pliku wejściowego

FS

Separator pól (domyślnie znaki białe)

RS

Separator rekordów (domyślnie koniec wiersza)

OFMT

Format wyjściowy dla liczb (domyślnie %g)

OFS

Separator pól dla wyjścia

ORS

Separator rekordów dla wyjścia

NF

Liczba pól w bieżącym rekordzie

Wartości zmiennych NR i FNR są takie same, jeśli przetwarzany jest jeden plik, ale jeśli przetwarzasz ich więcej, NR zawiera wartość będącą sumą liczby rekordów przeczytanych z wszystkich plików, a FNR - tylko z pliku bieżącego.

Aby zmienić separator pól wewnątrz skryptu, należy po prostu nadać nową wartość zmiennej FS; jeśli chcesz, by separatorem pól był dwukropek (jak w pliku /etc/passwd), użyj (na przykład w definicji akcji skojarzonej z wzorcem BEGIN) polecenia:

FS=":"

Zmiennych wewnętrznych można używać tak samo, jak wszystkich innych zmiennych. W poniższym przykładzie generowany jest komunikat o błędzie, jeśli rekord w pliku wejściowym ma zbyt mało pól:

NF <= 5 {print "Za malo pol w rekordzie"}

Instrukcje strukturalne

Wyjaśniliśmy już większość szczegółów niezbędnych do rozpoczęcia programowania w języku gawk. Teraz pora przyjrzeć się instrukcjom strukturalnym.

Jeśli masz już jakieś doświadczenie w programowaniu, instrukcje te na pewno wydadzą Ci się znajome. Nawet jeśli tak nie jest, powinieneś bez problemu zrozumieć zasady rządzące programowaniem języku gawk, ponieważ jest on bardzo prosty i nie zawiera żadnych udziwnionych reguł składniowych. Prześledź uważnie kilka podanych niżej przykładowych programów i spróbuj na własną rękę stworzyć programy pozwalające na wykonanie jakichś nieskomplikowanych zadań.

gawk pozwala na zamieszczanie w skryptach komentarzy, które oznaczane są za pomocą symbolu #. Powinieneś używać komentarzy w swoich skryptach, ponieważ pozwalają one osobie postronnej na szybkie zorientowanie się, o co chodzi w programie, a poza tym przysłużą się również Tobie, jeśli zajrzysz do swoich programów po dłuższym czasie.

Instrukcja if

Instrukcja if używana jest do testowania jakiegoś warunku, a następnie wykonywania pewnych operacji w zależności od wyniku takiego testu. Jej składnia jest następująca:

if (wyrażenie) {polecenia} else {polecenia}

wyrażenie może przyjmować dwie wartości: prawda lub fałsz. Oto prosty przykład:

# przyklad uzycia instrukcji if
(if ($1==0)

{
print "Komorka pierwsza ma wartosc 0"
}

else {
printf " Wartosc: %d\n",$1
})

Oczywiście program ten można zapisać w jednym wierszu, ale odpowiednie sformatowanie czyni go o wiele bardziej czytelnym. Łatwiej jest również wyłapać ewentualne błędy.

Powyższy skrypt sprawdza, czy w pierwszym polu rekordu znajduje się wartość 0 i jeśli tak jest, wyświetla komunikat Komorka pierwsza ma wartosc 0. W przeciwnym przypadku wyświetlana jest wartość zapisana w pierwszym polu.

Każda z sekcji instrukcji if może zawierać kilka poleceń, o ile są one otoczone nawiasami klamrowymi. Sekcja else nie jest obowiązkowa, może więc zostać pominięta:

(if ($1==0)

{

print "Ta komorka ma wartosc 0"
})

Język gawk, podobnie zresztą jak wiele innych języków programowania, posiada specjalną składnię dla prostych instrukcji if. Jest ona krótsza, ale i mniej przejrzysta, nie polecam jej więc nowicjuszom ani ludziom dbającym o porządek w swoim kodzie. Oto przykład instrukcji if w dwóch wariantach:

#wersja ladna
(if ($1>$2) {
print "Pierwsza kolumna jest wieksza"
}

else {
print "Druga kolumna jest wieksza"
})

#wersja krotka
$1>$2 {
print "Pierwsza kolumna jest wieksza"
}
{print "Druga kolumna jest wieksza"}

Jak widać, można pominąć słowa if oraz else. Pozostałe części struktury pozostają bez zmian: warunek, polecenia do wykonania, jeśli warunek jest spełniony, polecenia do wykonania, jeśli warunek nie jest spełniony. Taka składnia jest o wiele mniej czytelna, szczególnie jeśli nie wiesz, że jest to instrukcja if. Nie wszystkie wersje gawk obsługują tę składnię. To jeszcze jeden powód, by jej nie używać.

Pętla while

Instrukcja while pozwala na powtarzanie zestawu poleceń tak długo, jak długo spełniony jest jakiś warunek. Jest on sprawdzany przy każdym przejściu pętli. Jej składnia jest następująca:

while (wyrażenie) {
polecenia
}

Przykładowo, pętla while może zostać użyta do obliczenia, jaką kwotą będziesz dysponował po wpłaceniu na konto danej kwoty i odczekaniu pewnego czasu (odpowiedni wzór to wartosc=wklad(1+oprocentowanie)^lata):

#obliczanie procentu skladanego
#pobiera z pliku wklad, oprocentowanie i ilosc lat
{var =1
while (var <=$3) {
printf ("%f\n",$1*(1+$2)^var)
var++
}
}

Jak pewno zauważyłeś, skrypt inicjalizuje zmienną var wartością 1 jeszcze przed wejściem do pętli while. Bez tego zostałaby jej przypisana wartość 0. Interesujące nas zmienne odczytywane są z pliku wejściowego. Do inkrementacji licznika pętli (zmiennej var) używany jest przyrostek ++, powodujący zwiększenie wartości zmiennej o 1 przy każdym przebiegu pętli.

Pętla for

Przewagą pętli for nad pętlą while jest to, że pozwala ona na inicjalizację zmiennej sterującej wewnątrz pętli (zmienna ta jest inicjalizowana oczywiście tylko przy pierwszym przebiegu pętli). Oto jej składnia:

for (inicjalizacja; warunek; inkrementacja) {
polecenia
}

inicjalizacja odbywa się tylko przy pierwszym przejściu pętli. inkrementacja odbywa się przy każdym przebiegu. Zwykle inkrementacja sprowadza się do zwiększenia jakiegoś rodzaju licznika pętli, ale można w to miejsce wstawić dowolne polecenie. Poniżej zamieszczono przykład działający tak samo, jak przykład z poprzedniego podrozdziału, ale oparty na pętli for.

#obliczanie procentu skladanego
#pobiera z pliku wklad, oprocentowanie i ilosc lat
{ for (var=1; var <=$3 ; var++) {
printf ("%f\n",$1*(1+$2)^var)
}
}

Zmienna sterująca var zostaje przed wejściem do pętli zainicjalizowana wartością 1. Pętla działa tak długo, jak długo prawdziwy jest warunek var <= $3.Po każdym przebiegu pętli wartość zmiennej var jest zwiększana o 1.

Składnia tej pętli może wyglądać na nieco zagmatwaną, szczególnie jeśli nie miałeś wcześniej do czynienia z innymi językami programowania, ale jest po prostu kopią składni używanej w języku C.

next oraz exit

Polecenie next wymusza przejście do następnego rekordu, bez względu na wykonywane instrukcje. Na przykład w skrypcie:

{ polecenie1
polecenie2
polecenie3
next
polecenie4
}

po wykonaniu polecenia polecenie3 zawsze nastąpi przejście do następnego rekordu (i wykonanie polecenia polecenie1), co oznacza, że instrukcja polecenie4 nie zostanie nigdy wykonana.

Polecenie next zwykle używane jest wewnątrz pętli, aby wymusić jej opuszczenie po wystąpieniu jakiegoś warunku.

Polecenie exit powoduje, że gawk zachowuje się tak, jakby osiągnął koniec pliku, przechodząc do wykonania bloku END, jeśli taki istnieje, lub kończąc działanie. Jest ono użyteczne w przypadku, gdy wewnątrz skryptu wykryte zostaną nieprawidłowe dane i trzeba przerwać działanie programu.

Tablice

Język gawk obsługuje również tablice. Ich tworzenie i używanie nie wymaga specjalnych zabiegów, traktowane są podobnie jak wszystkie inne zmienne. Dostęp do elementu tablicy możliwy jest za pomocą indeksowania, na przykład:

var[numer]=7

Za przykład zastosowania tablicy niech posłuży skrypt, który umożliwia stworzenie pliku wyjściowego, w którym wiersze występują w odwrotnym porządku niż w pliku wejściowym:

#odwracanie kolejnosci wierszy
{ wiersze[NR] = $0 } #zapamietaj kazdy wiersz
END {licznik=NR #wypisanie w odwrotnym porzadku
while (licznik > 0) {

print wiersze[licznik]
licznik--
}
}

W tym prostym programie (spróbuj zrobić to samo w jakimkolwiek innym języku programowania, a przekonasz się, jak wydajny jest gawk) użyliśmy zmiennej wewnętrznej NR, oznaczającej liczbę przeczytanych wierszy. Zmiennej tej używamy do indeksowania tablicy wiersze[],zawierającej poszczególne wiersze pliku wejściowego. Po wczytaniu pliku zawartość tablicy wiersze[] jest wyświetlana zaczynając od ostatniego elementu. Nie trzeba deklarować tablicy ani martwić się o przydzielanie pamięci. Jest to znaczące ułatwienie przy pisaniu programów zorientowanych na pracę z plikami.

Podsumowanie

W tym rozdziale poruszyliśmy bardzo niewiele z rozlicznych zastosowań języka gawk, ale na pewno przekonałeś się, że jest to język wyjątkowo prosty. Jest on idealny do pisania krótkich programów, na przykład skryptów służących do obliczania rozmiarów plików i katalogów. W języku C wymagałoby to całkiem sporej ilości kodu, a w języku gawk wystarcza kilka wierszy.

Jeśli jesteś administratorem systemu, na pewno stwierdzisz wkrótce, że gawk jest doskonałym uzupełnieniem innych narzędzi, głównie ze względu na to, że potrafi obsłużyć mechanizmy przekierowania danych, oraz że może z powodzeniem wyręczyć Cię w większości codziennych obowiązków administratora systemu. Jeśli chcesz dowiedzieć się o nim czegoś więcej, zajrzyj na strony man albo poszukaj dobrej książki na jego temat.

Dalszych informacji o programowaniu możesz szukać w następnych rozdziałach.

Język C dla systemu Linux opisany jest w rozdziale 26. „Programowanie w języku C”.

Perl, inny bardzo poręczny język programowania, który dostarczany jest razem z Linuxem, przedstawiony jest w rozdziale 28. „Perl”.

Tcl oraz Tk, języki programowania umożliwiające korzystanie z interfejsu X oraz Motif, omówione są w rozdziale 29. „Wstęp do Tcl i Tk”.

428 E:\Moje dokumenty\HELION\Linux Unleashed\Indeks\25.DOC

E:\Moje dokumenty\HELION\Linux Unleashed\Indeks\25.DOC 427

428 Część V Linux dla programistów

Rozdzia³ 25. gawk 427



Wyszukiwarka

Podobne podstrony:
OPIS (25) DOC
b (25) doc
tor przeszkód kl 5ab 25 doc
~$25 doc
82 (25) DOC
25 (4) DOC
25' DOC
a (25) doc
Kopia CW 25 (2) DOC
25 DOC
ćw 25 doc
43 (25) DOC
ZESTAW 25 doc
FIZ 25 (2) DOC
25 (2) DOC
pytania (25) DOC
12 (25) DOC
swing w uliczce[36] kl 1 b gr 2 25 doc
Zag 25 Beata K doc

więcej podobnych podstron