Home Dokumentacje Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Czytanie plików wejściowych
23 | 10 | 2019
Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Czytanie plików wejściowych Drukuj

Przejdź do pierwszej, poprzedniej, następnej, ostatniej sekcji, spisu treści.

 


 

5. Czytanie plików wejściowych

W typowym programie awk, całość wejścia czytana jest albo ze standardowego wejścia (domyślnie z klawiatury, ale często potokiem z innego polecenia) albo z plików, których nazwy podano w wierszu poleceń awk. Jeżeli podano pliki wejściowe, to awk czyta je kolejno, odczytując wszystkie dane z jednego przed przejściem do następnego. Nazwę bieżącego pliku wejściowego można znaleźć w zmiennej wbudowanej FILENAME (zob. 10. Zmienne wbudowane).

Wejście czytane jest jednostkami zwanymi rekordami, i przetwarzane przez reguły danego programu po jednym rekordzie naraz. Domyślnie każdy rekord jest jednym wierszem. Każdy rekord jest automatycznie dzielony na kawałki zwane polami. Ułatwia to programom pracę z częściami rekordów.

Przy rzadkich okazjach zachodzi potrzeba zastosowania polecenia getline. Jest cenne, ponieważ potrafi bezpośrednio pobierać dane z dowolnej ilości plików, a ponadto pliki, których używa nie muszą być podane w wierszu poleceń awk (zob. 5.8. Odczyt bezpośredni przez getline).

5.1. Jak wejście dzielone jest na rekordy

Narzędzie awk dzieli wejście naszego programu awk na rekordy i pola. Rekordy oddzielane są znakiem nazywanym separatorem rekordów (record separator). Domyślnie separatorem rekordów jest znak nowej linii. Z tego powodu domyślnie rekordy są pojedynczymi wierszami. Jako separatora rekordów można użyć innego znaku, przypisując go zmiennej wbudowanej RS.

Wartość RS, jak każdej innej zmiennej programu awk, można zmienić za pomocą operatora przypisania `=' (zob. 7.7. Wyrażenia przypisania). Nowy znak separatora rekordów powinien być ujęty w znaki cudzysłowu, które sygnalizują stałą łańcuchową. Często odpowiednim do wykonania takiej zmiany miejscem jest początek wykonywania programu, przed przetworzeniem czegokolwiek z wejścia, tak by pierwszy rekord został odczytany za pomocą właściwego separatora. Można to osiągnąć wykorzystując specjalny wzorzec BEGIN (zob. 8.1.5. Wzorce specjalne BEGIN i END). Na przykład:

awk 'BEGIN { RS = "/" } ; { print $0 }' BBS-list

zmienia wartość RS na "/", przed odczytaniem czegokolwiek z wejścia. Pierwszym znakiem tego łańcucha jest ukośnik. W rezultacie, rekordy separowane są ukośnikami. Następnie czytany jest plik wejściowy, a druga reguła programu (akcja bez wzorca) wypisuje każdy rekord. Ponieważ każda instrukcja print na końcu swego wyjścia dodaje znak nowej linii, efektem pracy tego programu jest skopiowanie wejścia z każdym ukośnikiem zmienionym na znak nowej linii. A oto wyniki uruchomienia programu z plikiem `BBS-list':

$ awk 'BEGIN { RS = "/" } ; { print $0 }' BBS-list
-| aardvark     555-5553     1200
-| 300          B
-| alpo-net     555-3412     2400
-| 1200
-| 300     A
-| barfly       555-7685     1200
-| 300          A
-| bites        555-1675     2400
-| 1200
-| 300     A
-| camelot      555-0542     300               C
-| core         555-2912     1200
-| 300          C
-| fooey        555-1234     2400
-| 1200
-| 300     B
-| foot         555-6699     1200
-| 300          B
-| macfoo       555-6480     1200
-| 300          A
-| sdace        555-3430     2400
-| 1200
-| 300     A
-| sabafoo      555-2127     1200
-| 300          C
-|

Zauważ, że pozycja BBS-u `camelot' nie została podzielona. W pierwotnym pliku (zob. 1.3. Pliki danych do przykładów), odpowiedni wiersz wygląda tak:

camelot      555-0542     300               C

Ponieważ `camelot' ma tylko jedną prędkość transmisji, to w jego rekordzie nie ma ukośników.

Można też zmienić separator rekordów w inny sposób, w wierszu poleceń, wykorzystując możliwość przypisywania wartości zmiennej (zob. 14.2. Inne argumenty wiersza poleceń).

awk '{ print $0 }' RS="/" BBS-list

Przed przetworzeniem `BBS-list' RS nadawana jest wartość `/'.

Wykorzystanie jako separatora rekordów tak nietypowego znaku jak `/' daje w przeważającej większości przypadków prawidłowe zachowanie się programu. Jednak poniższy (skrajny) przykład potoku wypisuje zaskakujące `1'. Występuje tu tylko jedno pole, składające się ze znaku nowej linii. Wartością zmiennej wbudowanej NF jest liczba pól bieżącego rekordu.

$ echo | awk 'BEGIN { RS = "a" } ; { print NF }'
-| 1

Osiągnięcie końca pliku wejściowego kończy bieżący rekord wejściowy, nawet jeśli ostatni znak pliku nie jest znakiem z RS (c.k.).

Łańcuch pusty, "" (łańcuch nie zawierający żadnych znaków), ma specjalne znaczenie jako wartość RS: oznacza on, że rekordy są rozdzielone jednym lub wieloma wierszami pustymi, i niczym innym więcej. Zob. 5.7. Rekordy wielowierszowe, gdzie opisano szczegóły.

Jeżeli zmienimy wartość RS w środku działania awk, to nowa wartość będzie używana do rozdzielania kolejnych rekordów, ale nie będzie to mieć wpływu na aktualnie przetwarzany rekord (i rekordy już przetworzone).

Po wyznaczeniu końca rekordu gawk nadaje zmiennej RT (record terminator) wartość tekstu wejścia, który dopasował RS.

Wartość RS nie jest w rzeczywistości ograniczona do łańcucha jednoznakowego. Może być dowolnym wyrażeniem regularnym (zob. 4. Wyrażenia regularne). Ogólnie, każdy rekord kończy się na następnym łańcuchu pasującym do tego wyrażenia regularnego. Następny rekord zaczyna się na końcu dopasowanego łańcucha. Ta ogólna zasada działa faktycznie w zwykłym, codziennym przypadku, gdy RS zawiera tylko znak nowej linii: rekord kończy się u początku następnego pasującego łańcucha (następnego znaku nowej linii z wejścia) a kolejny rekord zaczyna się zaraz za końcem tego łańcucha (od pierwszego znaku kolejnego wiersza). Znak nowej linii, jako pasujący do RS, nie jest częścią żadnego z tych rekordów.

Jeśli RS jest pojedynczym znakiem, to RT będzie zawierać ten sam pojedynczy znak. Jednak, gdy RS będzie wyrażeniem regularnym, RT staje się bardziej przydatne. Zawiera wówczas faktyczny tekst wejściowy dopasowany wyrażeniem regularnym.

Poniższy przykład ilustruje obie te właściwości. Nadaje RS wartość wyrażenia regularnego dopasowującego albo znak nowej linii albo ciąg dużych liter z opcjonalnymi początkowymi i/lub końcowymi białymi znakami (zob. 4. Wyrażenia regularne).

$ echo rekord 1 AAAA rekord 2 BBBB rekord 3 |
> gawk 'BEGIN { RS = "\n|( *[[:upper:]]+ *)" }

>             { print "Rekord, $0, "a RT, RT }'
-| Rekord = rekord 1 a RT =  AAAA
-| Rekord = rekord 2 a RT =  BBBB
-| Rekord = rekord 3 a RT =
-|

Ostatni wiersz wyjścia ma dodatkowy pusty wiersz. Wynika to stąd, że wartością RT jest znak nowej linii, po którym instrukcja print dokłada swój własny końcowy znak nowej linii.

Zob. 16.2.8. Prosty edytor strumieniowy, gdzie umieszczono przydatniejszy przykład RS jako wyrażenia regularnego i wynikającego stąd RT.

Użycie RS jako wyrażenia regularnego i zmienna RT są rozszerzeniami. Nie są one dostępne w trybie zgodności (zob. 14.1. Opcje wiersza poleceń). W trybie tym do określenia końca rekordu używany jest tylko pierwszy znak wartości RS.

Narzędzie awk zapamiętuje liczbę rekordów, jakie do tej pory przeczytano z bieżącego pliku wejściowego. Wartość ta przechowywana jest we wbudowanej zmiennej o nazwie FNR. Jest ona zerowana przy rozpoczynaniu nowego pliku. Inna zmienna wbudowana, NR, Jest całkowitą liczbą rekordów przeczytanych dotąd ze wszystkich plików danych. Zaczyna się od zera, ale nigdy nie jest automatycznie zerowana.

5.2. Badanie pól

Przy odczycie przez awk rekordu wejściowego, jest on automatycznie rozdzielany, parsowany, przez interpreter na kawałki zwane polami (fields). Domyślnie pola rozdzielane są białymi znakami, podobnie jak słowa w wierszu.

Biały znak w awk oznacza łańcuch złożony z jednej lub więcej spacji, tabulacji lub znaków nowej linii; (5) inne znaki, jak formfeed (znak wysuwu strony), i tak dalej, uważane za białe znaki przez inne języki nie są białymi znakami dla awk.

Celem istnienia pól jest zapewnienie nam wygodniejszego dostępu przy odwoływaniu się do tych fragmentów rekordu. Nie musimy ich używać -- jeśli chcemy, możemy działać na całym rekordzie -- ale to dzięki polom proste programy awk są tak efektywne.

Odwołując się do pola w programie awk, używamy znaku dolara, `$', po którym występuje numer żądanego pola. Zatem, $1 odnosi się do pierwszego pola, $2 do drugiego, i tak dalej. Na przykład, załóżmy że mamy następujący wiersz wejścia:

To wygląda na całkiem ładny przykład.

Pierwszym polem, lub $1, jest tutaj `To'. Drugim polem, lub $2, jest `wygląda', i tak dalej. Zauważ, że ostatnim polem $7, jest `przykład.'. Ponieważ pomiędzy `d' a `.' nie ma odstępu, kropka uważana jest za część siódmego pola.

NF jest zmienną wbudowaną, której wartość jest liczbą pól bieżącego rekordu. awk automatycznie aktualizuje wartość NF za każdym razem, gdy czytany jest rekord.

Bez względu na to, ile jest pól, ostatnie pole można przedstawić jako $NF. Zatem w powyższym przykładzie $NF byłoby tym samym, co $7, czyli `przykład.'. Dlaczego działa taka notacja wyjaśniono poniżej (zob. 5.3. Numery pól nie będące stałymi). Próbując odwołać się do pola za ostatnim polem, jak na przykład $8 gdy rekord ma tylko siedem pól, otrzymujemy łańcuch pusty.

$0, wyglądający jak odwołanie do "zerowego" pola, jest przypadkiem specjalnym: reprezentuje cały rekord wejściowy. $0 wykorzystuje się, gdy nie jesteśmy zainteresowani polami:

Oto jeszcze kilka przykładów:

$ awk '$1 ~ /foo/ { print $0 }' BBS-list
-| fooey        555-1234     2400/1200/300     B
-| foot         555-6699     1200/300          B
-| macfoo       555-6480     1200/300          A
-| sabafoo      555-2127     1200/300          C

W tym przykładzie wypisywany jest każdy rekord pliku `BBS-list', którego pierwszego pole zawiera łańcuch `foo'. Operator `~' nazywany jest operatorem dopasowania (zob. 4.1. Jak stosować wyrażenia regularne). Sprawdza on czy łańcuch (tutaj: pole $1) pasuje do zadanego wyrażenia regularnego.

Poniższy przykład, w przeciwieństwie do poprzedniego, szuka `foo' w całym rekordzie i wypisuje pierwsze i ostatnie pole dla każdego dopasowanego rekordu wejściowego.

$ awk '/foo/ { print $1, $NF }' BBS-list
-| fooey B
-| foot B
-| macfoo A
-| sabafoo C

5.3. Numery pól nie będące stałymi

Liczba pól nie musi być stała. Po `$' może wystąpić dowolne wyrażenie języka awk. Wartość wyrażenia będzie określać numer pola. Jeżeli wartością jest łańcuch, a nie liczba, to jest zostanie przekształcony na liczbę. Rozważmy przykład:

awk '{ print $NR }'

Przypomnijmy, że NR jest liczbą dotychczas odczytanych rekordów: jeden przy pierwszym rekordzie, dwa przy drugim, itd. Przykład wypisuje więc pierwsze pole pierwszego rekordu, drugie pole drugiego rekordu, i tak dalej. Dla dwudziestego rekordu wypisywane jest pole numer 20. Najprawdopodobniej rekord ma mniej niż 20 pól, więc nasz kod wypisze pusty wiersz.

Oto inny przykład zastosowania wyrażeń jako numerów pól:

awk '{ print $(2*2) }' BBS-list

awk musi wyznaczyć wartość wyrażenia `(2*2)' i użyć jej jako numeru pola, jakie ma być wypisane. Znak `*' reprezentuje mnożenie, więc wyliczoną wartością wyrażenia `2*2' jest cztery. Ponieważ użyto nawiasów, mnożenie wykonywane jest przed operacją `$'. Są one niezbędne zawsze wtedy, gdy w wyrażeniu określającym numer pola występuje operator dwuargumentowy. Nasz przykład, zatem, wypisuje godziny pracy (czwarte pole) dla każdego wiersza pliku `BBS-list'. (Wszystkie operatory awk, w kolejności malejących priorytetów spisano w 7.14. Priorytet operatorów (Jak łączą się różne operatory).) Jeśli obliczony numer pola wynosi zero, to otrzymujemy cały rekord. Zatem, $(2-2) ma tę samą wartość, co $0. Ujemne numery pól są niedozwolone. Próba ich użycia na ogół przerywa działanie programu awk. (Standard POSIX nie definiuje, co się dzieje przy odwołaniu do ujemnego numeru pola. gawk zauważa taką sytuację i przerywa program. Inne implementacje awk mogą się inaczej zachowywać.)

Jak wspomniano w 5.2. Badanie pól, liczba pól bieżącego rekordu przechowywana jest w zmiennej wbudowanej NF (również zob. 10. Zmienne wbudowane). Wyrażenie $NF nie jest specjalną cechą: jest bezpośrednią konsekwencją wyznaczenia NF i użycia otrzymanej wartości jako numeru pola.

5.4. Zmiana zawartości pól

W programie awk można zmieniać zawartość pól widzianych przez awk. Zmienia to równocześnie to, co awk postrzega jako bieżący rekord wejściowy. (Faktyczne wejście pozostaje nietknięte; awk nigdy nie zmienia pliku wejściowego.)

Rozważmy taki przykład i jego wyniki:

$ awk '{ $3 = $2 - 10; print $2, $3 }' inventory-shipped
-| 13 3
-| 15 5
-| 15 5
...

Znak `-' reprezentuje odejmowanie, więc program przypisuje trzeciemu polu, $3, nową wartość równą wartości drugiego pola minus dziesięć, `$2 - 10'. (Zob. 7.5. Operatory arytmetyczne.) Następnie wypisywane jest pole numer dwa i nowa wartość pola numer trzy.

Tekst w polu $2 musi mieć sens jako liczba aby to zadziałało. Łańcuch znaków musi zostać przekształcony na liczbę, by komputer mógł na nim wykonać obliczenia arytmetyczne. Wynikła z odejmowania liczba jest powtórnie przekształcana na łańcuch znaków, który następnie staje się polem numer trzy. Zob. 7.4. Konwersja łańcuchów i liczb.

Przy zmianie wartości pola (postrzeganego przez awk), tekst rekordu wejściowego jest przeliczany tak, by zawierał nowe pole w miejscu starego. Stąd też, $0 zmienia się, by odzwierciedlić odmienione pole. Zatem, powyższy program wypisuje kopię pliku wejściowego, z dziesiątką odjętą od drugiego pola każdego wiersza.

$ awk '{ $2 = $2 - 10; print $0 }' inventory-shipped
-| Jan 3 25 15 115
-| Feb 5 32 24 226
-| Mar 5 24 34 228
...

Można także przypisać wartość polom spoza zakresu. Na przykład:

$ awk '{ $6 = ($5 + $4 + $3 + $2)
>        print $6 }' inventory-shipped
-| 168
-| 297
-| 301
...

Właśnie stworzyliśmy $6, którego wartością jest suma pól $2, $3, $4 i $5. Znak `+' reprezentuje dodawanie. W przypadku pliku `inventory-shipped' pole $6 przedstawia całkowitą liczbę paczek wysłanych w konkretnym miesiącu.

Tworzenie nowego pola zmienia używaną przez awk wewnętrzną kopię rekordu wejściowego -- wartość $0. Zatem, jeżeli po dodaniu pola wykonamy `print $0', to wypisany rekord będzie zawierał nowe pole, z odpowiednią ilością separatorów pól pomiędzy nim a uprzednio istniejącymi polami.

Przeliczenie to ma wpływ na wartość NF (liczbę pól; zob. 5.2. Badanie pól). Równocześnie zaś podlega wpływom zmiennej NF i, elementowi jeszcze nie omawianemu, separatorowi pól wyjściowych, OFS, używanemu do rozdzielania pól (zob. 6.3. Separatory wyjścia). Na przykład, wartość NF ustalana jest na numer najdalszego stworzonego przez nas pola.

Zauważ jednak, że samo odwołanie się do pola spoza zakresu nie zmienia wartości ani $0 ani NF. Odwołanie do pola spoza zakresu jedynie daje pusty łańcuch. Na przykład:

if ($(NF+1) != "")
    print "nie może wystąpić"
else
    print "wszystko normalnie"

powinno wypisać `wszystko normalnie', ponieważ NF+1 z pewnością będzie poza zakresem. (Zob. 9.1. Instrukcja if-else, gdzie znajduje się więcej informacji o if-else w awk. Zob. 7.10. Typy zmiennych i wyrażenia porównania, gdzie znaleźć można więcej szczegółów o operatorze `!='.)

Warto zapamiętać, że wykonanie przypisania do istniejącego pola zmieni wartość $0, ale nie zmieni wartości NF, nawet jeśli polu przypiszemy łańcuch pusty. Na przykład:

$ echo a b c d | awk '{ OFS = ":"; $2 = ""
>                       print $0; print NF }'
-| a::c:d
-| 4

Pole wciąż tu jest -- ma po prostu pustą wartość. Można to rozpoznać po obecności dwóch sąsiadujących ze sobą dwukropków.

Ten przykład pokazuje, co się dzieje, gdy tworzymy nowe pole.

$ echo a b c d | awk '{ OFS = ":"; $2 = ""; $6 = "nowe"
>                       print $0; print NF }'
-| a::c:d::nowe
-| 6

Wtrącone pole, $5 utworzone jest z pustą wartością (wskazaną przez druga parę sąsiadujących dwukropków). NF jest zaktualizowane wartością sześć.

Na koniec, pomniejszenie NF spowoduje utratę wartości pól po przeliczeniu nowych wartości NF i $0. Oto przykład:

$ echo a b c d e f | ../gawk '{ print "NF, NF;
>                               NF = 3; print $0 }'
-| NF = 6
-| a b c

5.5. Jak rozdzielać pola

Ta sekcja jest dość długa; opisuje jedną z najbardziej fundamentalnych w awk operacji.

5.5.1. Podstawy podziału na pola

separator pól (field separator), będący albo pojedynczym znakiem albo wyrażeniem regularnym, odpowiada za sposób, w jaki awk dzieli rekord wejściowy na pola. awk przegląda rekord wejściowy szukając ciągów znaków pasujących do separatora; same pola są tekstem pomiędzy dopasowaniami.

W poniższych przykładach posługujemy się symbolem wyliczenia "*" do przedstawienia spacji w wyjściu.

Jeżeli separatorem pól jest `oo', to poniższy wiersz:

moo goo gai pan

zostanie podzielony na trzy pola: `m', `*g' i `*gai*pan'. Zwróć uwagę na początkowe spacje w wartościach drugiego i trzeciego pola.

Separator pól reprezentowany jest przez zmienną wbudowaną FS. Uwaga programujący w powłoce! awk nie używa nazwy IFS wykorzystywanej przez powłoki zgodne z POSIX-em (jak powłoka Bourne'a, sh, czy GNU Bourne-Again Shell, Bash).

Wartość FS w programie awk zmieniamy za pomocą operatora przypisania, `=' (zob. 7.7. Wyrażenia przypisania). Często odpowiednim do tego momentem jest początek wykonywania programu, przed przetwarzaniem wejścia, tak by już pierwszy rekord został odczytany z właściwym separatorem. Robimy to wykorzystując wzorzec specjalny BEGIN (zob. 8.1.5. Wzorce specjalne BEGIN i END). Na przykład tutaj nadajemy zmiennej FS wartość ",":

awk 'BEGIN { FS = "," } ; { print $2 }'

Przy wierszu wejściowym,

John Q. Smith, 29 Oak St., Walamazoo, MI 42139

program ten wydobywa i wypisuje łańcuch `*29*Oak*St.'.

Czasem dane wejściowe zawierać będą znaki separujące, które nie rozdzielają pól w sposób, jakiego byśmy się spodziewali. Dajmy na to, imię i nazwisko osoby w ostatnio użytym przykładzie może mieć dołączony tytuł czy inny przyrostek, jak `John Q. Smith, LXIX'. Z wejścia zawierającego takie dane osobowe:

John Q. Smith, LXIX, 29 Oak St., Walamazoo, MI 42139

powyższy program wydzieliłby `*LXIX', zamiast `*29*Oak*St.'. Jeżeli spodziewaliśmy się, że program wypisze adres, będziemy zaskoczeni. Morał: należy ostrożnie dobierać układ danych i znaki separujące, by zapobiec takim kłopotom.

Jak już wiemy, normalnie pola separowane są sekwencjami białych znaków (spacji, tabulacji i znaków nowej linii), nie przez pojedyncze spacje: dwie kolejne spacje nie rozgraniczają pustego pola. Domyślną wartością separatora pól FS jest łańcuch zawierający pojedynczą spację, " ". Gdyby ta wartość była interpretowana w zwykły sposób, to każdy znak spacji rozdzielałby pola, zatem dwie sąsiednie spacje tworzyłyby puste pole pomiędzy nimi. Powodem, dla którego się tak nie dzieje, jest to, że pojedyncza spacja jako wartość FS jest przypadkiem specjalnym: traktowana jest jako określenie domyślnego sposobu rozgraniczania pól.

Jeżeli FS jest jakimś innym pojedynczym znakiem, to każde wystąpienie tego znaku oddziela od siebie dwa pola. Dwa sąsiadujące wystąpienia ograniczają pole puste. Jeśli znak ten pojawia się na początku lub końcu wiersza, to również oddziela puste pole. Znak spacji jest jedynym pojedynczym znakiem nie przestrzegającym tych zasad.

5.5.2. Stosowanie wyrażeń regularnych do podziału na pola

Poprzednia podsekcja omawiała stosowanie jako wartości FS pojedynczych znaków lub zwykłych łańcuchów. Ogólniej, wartością FS może być łańcuch zawierający dowolne wyrażenie regularne. W takim przypadku, każde dopasowanie tego wyrażenia w rekordzie separuje pola. Na przykład, przypisanie:

FS = ", \t"

czyni ogranicznikiem każdy obszar rekordu wejściowego składający się z przecinka z umieszczoną po nim spacją i tabulacją. (`\t' jest sekwencją specjalną oznaczającą tabulację; zob. 4.2. Sekwencje specjalne, gdzie znajduje się pełna lista podobnych sekwencji specjalnych.)

Jako mniej banalny przykład z wyrażeniem regularnym, załóżmy, że chcielibyśmy, by pojedyncze spacje rozdzielały pola w taki sam sposób, jak powyżej użyliśmy przecinków. Możemy nadać FS wartość "[ ]" (lewy nawias kwadratowy, spacja, prawy nawias kwadratowy). To wyrażenie regularne dopasowuje pojedynczą spację i nic więcej (zob. 4. Wyrażenia regularne).

Pomiędzy dwoma przypadkami: `FS = " "' (pojedyncza spacja) a `FS = "[ \t\n]+"' (lewy nawias kwadratowy, spacja, odwrotny ukośnik, "t", odwrotny ukośnik, "n", prawy nawias kwadratowy, co tworzy wyrażenie regularne dopasowujące jeden lub więcej znaków spacji, tabulacji lub nowej linii), istnieje istotna różnica. Przy obu wartościach FS pola rozdzielane są ciągami spacji, tabulacji i/lub znaków nowej linii. Jeżeli jednak wartością FS jest " ", to awk będzie usuwał z rekordu początkowe i końcowe białe znaki, a dopiero następnie decydował, gdzie znajdują się pola.

Na przykład, poniższy potok wypisuje `b':

$ echo ' a b c d ' | awk '{ print $2 }'
-| b

Jednak ten potok wypisze `a' (zwróć uwagę na dodatkowe spacje wokół każdej z liter):

$ echo ' a  b  c  d ' | awk 'BEGIN { FS = "[ \t]+" }
>                                  { print $2 }'
-| a

W tym przypadku, pierwsze pole jest puste, czyli jest łańcuchem zerowym, pustym.

Obcinanie początkowych i końcowych białych znaków odbywa się także podczas przeliczania wartości $0. Na przykład, przeanalizujmy taki potok:

$ echo '   a b c d' | awk '{ print; $2 = $2; print }'
-|    a b c d
-| a b c d

Pierwsza instrukcja print wypisuje rekord tak, jak został odczytany, z nienaruszonym początkowym białym znakiem. Przypisanie do $2 przebudowuje $0 przez złączenie razem $1 do $NF, rozdzielonych wartością OFS. Ponieważ przy wykrywaniu $1 zignorowany został początkowy biały znak, nie stał się on częścią nowego $0. Ostatecznie, ostatnia instrukcja print wypisuje nowe $0.

5.5.3. Jak z każdego znaku zrobić osobne pole

Zdarza się, że chcemy badać każdy znak rekordu z osobna. W gawk jest to łatwe, po prostu przypisujemy pusty łańcuch ("") do FS. W tym przypadku, każdy pojedynczy znak rekordu stanie się odrębnym polem. Oto przykład:

$ echo a b | gawk 'BEGIN { FS = "" }
>                  {
>                      for (i = 1; i <= NF; i = i + 1)

>                          print "W polu", i, "jest", $i
>                  }'
-| W polu 1 jest a
-| W polu 2 jest
-| W polu 3 jest b

Tradycyjnie, zachowanie się programu przy FS równym "" nie było zdefiniowane. W takim przypadku uniksowy awk traktowałby cały rekord jako mający tylko jedno pole (c.k.). W trybie zgodności (zob. 14.1. Opcje wiersza poleceń), gawk także będzie się zachowywać się w ten sposób, jeśli FS będzie łańcuchem pustym.

5.5.4. Ustalanie FS z wiersza poleceń

Zmiennej FS można nadać wartość w wierszu poleceń. Wykorzystujemy do tego opcję `-F'. Na przykład:

awk -F, 'program' pliki-wejściowe

powoduje, że FS będzie znakiem `,'. Zauważ, że opcja ta używa dużej litery `F'. W przeciwieństwie do niej, małe `-f' określa plik zawierający program awk. W opcjach wiersza poleceń wielkość liter jest istotna: opcje `-F' i `-f' nie mają ze sobą nic wspólnego. Można stosować obie równocześnie, do nadania wartości zmiennej FS i pobrania programu awk z pliku.

Wartość użyta jako argument opcji `-F' przetwarzana jest w dokładnie taki sam sposób, jak przypisania do zmiennej wbudowanej FS. To znaczy, że jeżeli separator pól zawiera znaki specjalne, to muszą one być we właściwy sposób cytowane. Na przykład, chcąc jako separator pól zastosować `\', musielibyśmy wpisać:

# to samo, co FS = "\\"
awk -F\\\\ '...' pliki ...

Ponieważ `\' służy w powłoce do cytowania znaków, awk zobaczy `-F\\'. Następnie awk przetwarza `\\' używając znaków specjalnych (zob. 4.2. Sekwencje specjalne), ostatecznie dając pojedynczy `\' stosowany jako separator rekordów.

Przypadkiem specjalnym jest to, że w trybie zgodności (zob. 14.1. Opcje wiersza poleceń), jeżeli argumentem `-F' jest `t', to FS otrzymuje wartość znaku tabulacji. Jest to spowodowane tym, że jeśli wpiszemy w powłoce `-F\t', bez żadnych cudzysłowów, to `\' zostanie usunięty, więc awk stwierdza, że naprawdę chcieliśmy, by pola były rozdzielane tabulacjami, a nie literami `t'. Jeżeli rzeczywiście chcemy rozdzielać pola literami `t', to w wierszu poleceń powinniśmy użyć `-v FS="t"' (zob. 14.1. Opcje wiersza poleceń).

Na przykład, skorzystajmy z pliku programu awk o nazwie `baud.awk', zawierającego wzorzec /300/ i akcję `print $1'. Oto ten program:

/300/   { print $1 }

Ustalimy też wartość FS na znak `-', i uruchomimy program z plikiem danych `BBS-list'. Poniższe polecenie wypisuje nazwy BBS-ów, które działają z prędkością 300 baud, z pierwszymi trzema cyframi numerów telefonów:

$ awk -F- -f baud.awk BBS-list
-| aardvark     555
-| alpo
-| barfly       555
...

Zwróć uwagę na pierwszy wiersz wynikowy. W pierwotnym pliku (zob. 1.3. Pliki danych do przykładów), drugi wiersz wygląda tak:

alpo-net     555-3412     2400/1200/300     A

Zamiast `-' w numerze telefonu, jak to było zamierzone, jako separator rekordów został zastosowany znak `-' będący częścią nazwy systemu. Pokazuje to, dlaczego powinniśmy być rozważni przy wyborze separatorów pól i rekordów.

Na wielu systemach uniksowych każdy z użytkowników ma osobny wpis w pliku haseł, po jednym wierszu na użytkownika. Dane w tych wierszach są rozdzielane dwukropkami. Pierwsze pole jest nazwą zgłoszeniową użytkownika (login), a drugie jego zaszyfrowanym hasłem. Pozycja w pliku haseł może wyglądać tak:

arnold:xyzzy:2076:10:Arnold Robbins:/home/arnold:/bin/sh

Poniższy program przeszukuje systemowy plik haseł i wypisuje pozycje użytkowników nie mających haseł:

awk -F: '$2 == ""' /etc/passwd

5.5.5. Podsumowanie podziału na pola

Zgodnie ze standardem POSIX, awk powinien zachowywać się tak, jakby każdy rekord był dzielony na pola w momencie jego odczytu. W szczególności oznacza to, że możemy zmienić wartość FS po przeczytaniu rekordu, a wartości pól (tj. sposób, w jaki są podzielone) powinny odzwierciedlać starą wartość FS, a nie nową.

Jednak wiele implementacji awk nie działa w ten sposób. Zamiast tego, odkładają podział na pola do momentu, gdy wystąpi faktyczne odwołanie się pola. Pola zostaną rozdzielone przy zastosowaniu bieżącej wartości FS! (c.k.) Zachowanie takie może być trudne do rozpoznania. Poniższy przykład ilustruje różnicę pomiędzy tymi dwoma metodami. (Polecenie sed(6) wypisuje tylko pierwszy wiersz `/etc/passwd'.)

sed 1q /etc/passwd | awk '{ FS = ":" ; print $1 }'

zwykle wypisze

root

w niepoprawnej implementacji awk, podczas gdy gawk wypisze coś w rodzaju

root:nSijPlPhZZwgE:0:0:Root:/:

Poniższa tabela podsumowuje sposoby podziału na pola, w zależności od wartości FS. (`==' oznacza "jest równe".)

FS == " "
Pola oddzielane są ciągami białych znaków. Początkowe i końcowe białe znaki są ignorowane. Jest to ustawienie domyślne.
FS == inny pojedynczy znak
Pola rozdzielane są każdym wystąpieniem zadanego znaku. Wielokrotne sąsiednie wystąpienia rozgraniczają puste pola, tak samo jak wystąpienia na początku i na końcu rekordu. Znak separujący może być nawet metaznakiem wyrażeń regularnych; nie musi być cytowany.
FS == regexp
Pola oddzielane są wystąpieniami znaków pasujących do regexp. Początkowe i końcowe dopasowania regexp rozdzielają puste pola.
FS == ""
Każdy poszczególny znak rekordu staje się pojedynczym polem.

5.6. Czytanie danych o stałej szerokości

(Niniejsza sekcja opisuje cechę rozszerzoną, eksperymentalną. Początkujący użytkownicy awk mogą ją pominąć przy pierwszym czytaniu.)

W wersji 2.13 gawk wprowadzono nową funkcję do obsługi pól o stałych szerokościach, bez wyróżnialnego separatora pól. Dane tego rodzaju pojawiają się, na przykład, jako wejście starych programów w FORTRAN-ie, gdzie liczby następują bezpośrednio po sobie; albo jako wyjście programów nie przewidujących zastosowania go jako wejścia dla innych programów.

Przykładem tego ostatniego jest tabela, gdzie wszystkie kolumny wyrównano za pomocą zmiennej liczby spacji a puste pola są po prostu spacjami. Jasne jest, że w tym przypadku normalny podział na pola oparty na FS nie zadziała dobrze. Mimo, że przenośny program awk może stosować serie wywołań substr w odniesieniu do $0 (zob. 12.3. Funkcje wbudowane działające na łańcuchach), jest to niewygodne i nieefektywne przy większej liczbie pól.

Podział rekordu wejściowego na pola o stałej szerokości wyszczególniany jest przez przypisanie zmiennej wbudowanej FIELDWIDTHS łańcucha zawierającego rozdzielone spacjami liczby. Każda z nich określa szerokość pola łącznie z kolumnami pomiędzy polami. Chcąc pominąć kolumny pomiędzy polami, podajemy ich szerokości jako odrębne pola, później ignorowane.

Poniższe dane są wyjściem z uniksowego narzędzia w. Przydadzą się jako ilustracja zastosowania FIELDWIDTHS.

 10:06pm  up 21 days, 14:04,  23 users
User     tty       login  idle   JCPU   PCPU  what
hzuo     ttyV0     8:58pm            9      5  vi p24.tex
hzang    ttyV3     6:37pm    50                -csh
eklye    ttyV5     9:53pm            7      1  em thes.tex
dportein ttyV6     8:17pm  1:47                -csh
gierd    ttyD3    10:00pm     1                elm
dave     ttyD4     9:47pm            4      4  w
brent    ttyp0    26Jun91  4:46  26:46   4:41  bash
dave     ttyq4    26Jun9115days     46     46  wnewmail

Pokazany niżej program pobiera powyższe wejście, przekształca czas nieaktywności (iddle time) na liczbę sekund i wypisuje pierwsze dwa pola oraz wyliczony czas nieaktywności. (Program wykorzystuje kilka cech awk, o których jeszcze nie mówiono.)

BEGIN  { FIELDWIDTHS = "9 6 10 6 7 7 35" }
NR > 2 {
    idle = $4
    sub(/^  */, "", idle)   # obcina początkowe spacje
    if (idle == "")
        idle = 0
    if (idle ~ /:/) {
        split(idle, t, ":")
        idle = t[1] * 60 + t[2]
    }
    if (idle ~ /days/)
        idle *= 24 * 60 * 60

    print $1, $2, idle
}

Oto wyniki działania programu z naszymi danymi:

hzuo      ttyV0  0
hzang     ttyV3  50
eklye     ttyV5  0
dportein  ttyV6  107
gierd     ttyD3  1
dave      ttyD4  0
brent     ttyp0  286
dave      ttyq4  1296000

Innym (zapewne bardziej praktycznym) przykładem danych wejściowych o stałej szerokości byłoby wejście ze sterty kart do głosowania. W niektórych częściach Stanów Zjednoczonych głosujący zaznaczają swój wybór robiąc dziurki w kartach komputerowych. Następnie karty są przetwarzane, w celu zliczenia głosów na konkretnego kandydata czy jakąś sporną kwestię. Ponieważ uprawniony może zdecydować o nieoddaniu głosu w jakiejś sprawie, każda kolumna karty może być pusta. Program awk do przetwarzania takich danych mógłby wykorzystać funkcję FIELDWIDTHS do uproszczenia czytania. (Oczywiście, znalezienie gawk działającego na systemie z czytnikami kart jest osobną sprawą!)

Przypisanie wartości do FS powoduje, że gawk powraca do stosowania FS do podziału na pola. By to spowodować wystarczy użyć `FS = FS', bez potrzeby znajomości aktualnej wartości FS.

Funkcja ta jest wciąż eksperymentalna i może się z czasem zmieniać. Zauważ, że w szczególności, gawk nie usiłuje zweryfikować poprawności wartości użytych w FIELDWIDTHS.

5.7. Rekordy wielowierszowe

W niektórych bazach danych pojedynczy wiersz nie może w wygodny sposób przechować całości informacji o jednej pozycji. W takich przypadkach możemy użyć rekordów wielowierszowych.

Pierwszym krokiem do tego jest wybór formatu danych: skoro rekordy nie są zdefiniowane jako pojedyncze wiersze, to jak chcemy je zdefiniować? Co powinno rozdzielać rekordy?

Jedną z technik jest użycie do rozdzielania rekordów jakiegoś niecodziennego znaku czy łańcucha. Na przykład, możemy wykorzystać do tego znak wysuwu strony (w awk, podobnie jak w C, zapisywany jako `\f'), robiąc rekordem każdą stronę pliku. Równie dobrze może być zastosowany każdy inny znak, pod warunkiem, że nie będzie on częścią danych rekordu.

Inną techniką jest rozdzielanie rekordów pustymi wierszami. Na zasadzie specjalnego wyjątku pusty łańcuch jako wartość RS wskazuje, że rekordy są oddzielane jednym lub wieloma pustymi wierszami. Następny rekord zaś nie rozpoczyna się aż do napotkania pierwszego po nich niepustego wiersza -- bez względu na to, ile wystąpi kolejnych pustych wierszy, są one uważane za jeden separator rekordu.

Ten sam efekt, co przy `RS = ""', można osiągnąć przypisując RS wartość "\n\n+". To wyrażenie regularne dopasowuje znak nowej linii na końcu rekordu, i jednej lub więcej pustych wierszy po rekordzie. Dodatkowo, wyrażenia regularne, jeśli jest wybór, zawsze dopasowują pierwszą z lewej najdłuższą sekwencję (zob. 4.6. Jak bardzo pasuje tekst?). Zatem następny rekord nie rozpocznie się aż do napotkania występującego po bieżącym niepustego wiersza -- bez względu na to, ile wystąpi kolejnych pustych wierszy, są one uważane za jeden separator rekordu.

Istnieje istotna różnica pomiędzy `RS = ""' a `RS = "\n\n+"'. W pierwszym przypadku, początkowe znaki nowej linii z pliku wejściowego są ignorowane, a jeśli plik kończy się dodatkowymi pustymi wierszami po ostatnim rekordzie, to ostatni znak nowej linii jest z rekordu usuwany. W drugim przypadku, to specjalne przetwarzanie nie jest wykonywane (c.k.).

Teraz, gdy wejście jest już podzielone na rekordy, drugim krokiem jest rozdzielenie pól rekordu. Jedną z metod jest podział każdego z wierszy na pola w zwykły sposób. Dzieje się tak domyślnie w wyniku specjalnej cechy: gdy RS jest łańcuchem pustym, znak nowej linii zawsze działa jako separator pól. Jest to wykonywane dodatkowo oprócz podziałów na pola wynikających z FS.

Pierwotnym powodem tego specjalnego wyjątku było prawdopodobnie zapewnienie przydatnego zachowania się programu w przypadku domyślnym (tj. FS jest równe " "). Cecha ta może być kłopotliwa, jeżeli faktycznie nie chcemy rozdzielania rekordów przez znak nowej linii, gdyż nie ma sposobu by tego uniknąć. Jednak można to obejść wykorzystując funkcję split do ręcznego podziału rekordu (zob. 12.3. Funkcje wbudowane działające na łańcuchach).

Inną metodą podziału na pola jest umieszczenie każdego pola w osobnym wierszu: do jej wykorzystania wystarczy przypisać zmiennej FS łańcuch "\n". (To proste wyrażenie regularne dopasowuje pojedynczy znak nowej linii.)

Praktycznym przykładem pliku danych zorganizowanego w ten sposób może być lista adresowa, gdzie każda pozycja oddzielona jest pustymi wierszami. Załóżmy, że mamy taką listę w pliku `addresses':

Jane Doe
123 Main Street
Anywhere, SE 12345-6789

John Smith
456 Tree-lined Avenue
Smallville, MW 98765-4321
...

Prosty program do przetwarzania tych danych może być taki:

# addrs.awk --- prosty program listy adresowej

# Rekordy oddzielone są pustymi wierszami
# każdy wiersz jest jednym polem.
BEGIN { RS = "" ; FS = "\n" }

{
      print "Name is:", $1
      print "Address is:", $2
      print "City and State are:", $3
      print ""
}

Uruchomienie programu daje następujące wyjście:

$ awk -f addrs.awk addresses
-| Name is: Jane Doe
-| Address is: 123 Main Street
-| City and State are: Anywhere, SE 12345-6789
-|
-| Name is: John Smith
-| Address is: 456 Tree-lined Avenue
-| City and State are: Smallville, MW 98765-4321
-|
...

Zob. 16.2.4. Wypisywanie etykiet adresowych, gdzie umieszczono bardziej realistyczny program zajmujący się listami adresowymi.

Poniższa tabela podsumowuje sposoby podziału na rekordy, w zależności od wartości RS. (`==' oznacza "jest równe".)

RS == "\n"
Rekordy rozdzielane są znakiem nowej linii (`\n'). W wyniku tego, każdy wiersz pliku danych, także pusty, jest osobnym rekordem. Jest to ustawienie domyślne.
RS == inny pojedynczy znak
Rekordy są oddzielane każdym wystąpieniem zadanego znaku. Wielokrotne sąsiednie wystąpienia rozgraniczają puste rekordy.
RS == ""
Rekordy są rozdzielane ciągami pustych wierszy. Znak nowej linii zawsze służy jako separator rekordu, dodatkowo, oprócz wartości FS. Początkowe i końcowe znaki nowej linii w pliku są ignorowane.
RS == regexp
Rekordy rozdzielane są wystąpieniami znaków pasujących do regexp. Początkowe i końcowe dopasowania regexp oddzielają puste rekordy.

We wszystkich przypadkach gawk przypisuje zmiennej RT tekst wejściowy, jaki pasował do wartości podanej przez RS.

5.8. Odczyt bezpośredni przez getline

Do tej pory pobieraliśmy dane wejściowe z głównego strumienia wejściowego awk -- albo standardowego wejścia (zwykle terminala, czasami wyjścia innego programu) albo z plików wyszczególnionych w wierszu poleceń. Język awk ma specjalne polecenie wbudowane o nazwie getline, które stosuje się czytania wejścia pod bezpośrednią kontrolą programisty.

5.8.1. Wprowadzenie do getline

Polecenie to stosowane jest na kilka różnych sposobów i nie powinno być używane przez początkujących. Jest opisane tutaj, gdyż rozdział traktuje o wejściu. Przykłady występujące po objaśnieniu polecenia getline zawierają jeszcze nie omawiany materiał. Z tego powodu, powinieneś powrócić do tej części i przestudiować polecenie getline po przeglądnięciu reszty książki i nabraniu dobrej znajomości sposobu działania awk.

getline zwraca jeden jeśli znajdzie rekord, a zero jeśli napotkano koniec pliku. Jeżeli podczas pobierania rekordu pojawi się błąd, jak wtedy, gdy plik nie może zostać otwarty, to getline zwraca -1. W tym przypadku gawk przypisuje zmiennej ERRNO łańcuch opisujący zaistniały błąd.

W kolejnych przykładach, polecenie oznacza wartość łańcuchową reprezentującą polecenie powłoki.

5.8.2. Użycie getline bez argumentów

Polecenie getline bez argumentów służy do czytania wejścia z bieżącego pliku wejściowego. W tym przypadku odczytuje ono tylko kolejny rekord wejściowy i rozbija go na pola. Przydatne, gdy zakończyliśmy przetwarzanie bieżącego rekordu, ale chcemy od razu wykonać specjalne przetwarzanie następnego. Oto przykład:

awk '{
     if ((t = index($0, "/*")) != 0) {
          # wartością będzie "" jeśli t jest 1
          tmp = substr($0, 1, t - 1)
          u = index(substr($0, t + 2), "*/")
          while (u == 0) {
               if (getline <= 0) {
                    m = "unexpected EOF or error"
                    m = (m ": " ERRNO)
                    print m > "/dev/stderr"
                    exit
               }
               t = -1
               u = index($0, "*/")
          }
          # wyrażenie będzie równe "" jeśli */
          # pojawiło się na końcu wiersza
          $0 = tmp substr($0, t + u + 3)
     }
     print $0
}'

Ten program awk usuwa z wejścia wszystkie komentarze typu używanego w C, `/* ... */'. Zastępując `print $0' innymi instrukcjami, można wykonywać bardziej skomplikowane przetwarzanie odkomentowanego wejścia, na przykład, wyszukiwanie dopasowań wyrażenia regularnego. Program ma drobny feler -- nie działa jeśli w tym samym wierszu kończy się jeden z komentarzy a zaczyna inny.

Taka postać polecenia getline ustala nowe wartości NF (liczba pól; zob. 5.2. Badanie pól), NR (liczba dotychczas odczytanych rekordów; zob. 5.1. Jak wejście dzielone jest na rekordy), FNR (liczba rekordów przeczytanych z tego pliku wejściowego) oraz $0.

Zauważ: nowa wartość $0 jest używana przy sprawdzaniu wzorców ewentualnych kolejnych reguł. Pierwotna wartość $0, jaka wywołała regułę, w której wykonano getline jest tracona (c.k.). Natomiast instrukcja next postępuje inaczej: czyta nowy rekord, lecz natychmiast rozpoczyna jego normalne przetwarzanie, poczynając od pierwszej reguły programu. Zob. 9.7. Instrukcja next.

5.8.3. Użycie getline do zmiennej

Konstrukcję `getline zmn' wykorzystujemy do wczytania następnego rekordu z wejścia awk do zmiennej zmn. Nie jest dokonywane żadne inne przetwarzanie.

Załóżmy na przykład, że następny wiersz jest komentarzem lub łańcuchem specjalnym, i chcemy go przeczytać bez wyzwalania żadnej z reguł. Ta postać getline pozwala na odczyt wiersza i zachowanie go w zmiennej, tak że główna pętla awk, czytaj-wiersz-i-sprawdź-każdą-regułę, nigdy go nie zauważy.

Poniższy przykład zamienia miejscami każde dwa wiersze wejścia. Na przykład, przy podanych:

wan
tew
free
phore

wypisuje:

tew
wan
phore
free

Oto program:

awk '{
     if ((getline tmp) > 0) {
          print tmp
          print $0
     } else
          print $0
}'

Polecenie getline użyte w ten sposób nadaje jedynie wartości zmiennym NR i FNR (i oczywiście, zmn). Rekord nie jest dzielony na pola, więc wartości pól (łącznie z $0) i wartość NF nie zmieniają się.

5.8.4. Użycie getline z pliku

Konstrukcję `getline < plik' stosujemy do odczytu następnego rekordu z pliku plik. plik jest tu określającym nazwę pliku wyrażeniem o wartości łańcuchowej. `< plik' jest zwane przekierowaniem, gdyż skierowuje wejście tak, by pochodziło z innego miejsca.

Na przykład, poniższy program napotkając pierwsze pole o wartości 10 w bieżącym pliku wejściowym czyta rekord wejściowy z pliku `secondary.input'.

awk '{
    if ($1 == 10) {
         getline < "secondary.input"
         print
    } else
         print
}'

Ponieważ nie jest używany główny strumień wejściowy, wartości NR i FNR pozostają bez zmian. Rekord jest jednak dzielony na pola w normalny sposób, więc zmieniają się wartości $0 i pozostałych pól. Także wartość NF.

Zgodnie ze standardem POSIX, `getline < wyrażenie' jest niejednoznaczne jeśli wyrażenie zawiera nieujęte w nawiasy operatory inne niż `$'. Na przykład, `getline < katalog "/" plik' jest niejednoznaczne, bo operator konkatenacji nie został umieszczony w nawiasach, i powinno być zapisane jako `getline < (katalog "/" plik)' jeśli chcemy, by program był przenośny na inne implementacje awk.

5.8.5. Użycie getline z pliku do zmiennej

Konstrukcja `getline zmn < plik' służy do odczytu wejścia z pliku plik i umieszczenia go w zmiennej zmn. Jak powyżej, plik jest wyrażeniem o wartości łańcuchowej określającym nazwę pliku, z którego ma nastąpić czytanie.

W tej wersji getline, nie jest zmieniana żadna ze zmiennych wbudowanych, a rekord nie jest dzielony na pola. Jedyną zmienianą zmienną jest zmn.

Na przykład, poniższy program kopiuje wszystkie pliki wejściowe na wyjście, za wyjątkiem rekordów zawierających `@include nazwapliku'. Taki rekord zastępowany jest zawartością pliku nazwapliku.

awk '{
     if (NF == 2 && $1 == "@include") {
          while ((getline line < $2) > 0)
               print line
          close($2)
     } else
          print
}'

Zauważ tu, że nazwa dodatkowego pliku wejścia nie jest wbudowana w program. Brana jest wprost z danych, z drugiego pola wiersza `@include'.

Funkcja close wywoływana jest w celu zagwarantowania, że jeżeli w wejściu pojawią się dwa identyczne wiersze `@include', to cały wyszczególniony przez nie plik będzie włączony dwukrotnie. Zob. 6.8. Zamykanie potoków oraz plików wejściowych i wyjściowych.

Jednym z braków tego programu jest to, że nie przetwarza on zagnieżdżonych instrukcji `@include' (instrukcji `@include' w dołączanych plikach), jak robiłby to prawdziwy preprocesor makr. Zob. 16.2.9. Łatwa metoda korzystania z funkcji bibliotecznych, gdzie podano program obsługujący zagnieżdżone instrukcje `@include'.

5.8.6. Użycie getline z potoku

Do getline można przekazać potokiem wyjście dowolnego polecenia, korzystając ze składni `polecenie | getline'. W takim przypadku, łańcuch polecenie uruchamiany jest jako polecenie powłoki, a jego wyjście przekazywane jest potokiem do awk, gdzie będzie użyte jako wejście. Ta postać getline czyta z potoku po jednym rekordzie naraz.

Na przykład, poniższy program kopiuje swoje wejście na wyjście, z wyjątkiem wierszy rozpoczynających się od `@execute', które są zastępowane wyjściem utworzonym przez uruchomienie pozostałej części wiersza jako polecenia powłoki:

awk '{
     if ($1 == "@execute") {
          tmp = substr($0, 10)
          while ((tmp | getline) > 0)
               print
          close(tmp)
     } else
          print
}'

Funkcja close jest wywoływana w celu zagwarantowania, że jeśli w wejściu pojawią się dwa identyczne wiersze `@execute', to polecenie zostanie wykonane dla każdego z nich. Zob. 6.8. Zamykanie potoków oraz plików wejściowych i wyjściowych.

Przy podanym wejściu:

foo
bar
baz
@execute who
bletch

program ten może utworzyć:

foo
bar
baz
arnold     ttyv0   Jul 13 14:22
miriam     ttyp0   Jul 13 14:23     (murphy:0)
bill       ttyp1   Jul 13 14:23     (murphy:0)
bletch

Zwróć uwagę, że program uruchomił polecenie who i wypisał wynik. (Jeśli wypróbujesz go we własnym systemie, otrzymasz oczywiście inne rezultat, pokazujący, kto jest zalogowany w twoim systemie.)

Ta odmiana getline dokonuje podziału rekordu na pola, nadaje wartość NF i przelicza wartość $0. Wartości NR i FNR nie są zmieniane.

Zgodnie ze standardem POSIX, `wyrażenie | getline' jest niejednoznaczne jeśli wyrażenie zawiera nieujęte w nawiasy operatory inne niż `$'. Na przykład, `"echo " "date" | getline' jest niejednoznaczne, bo operator konkatenacji nie został umieszczony w nawiasach, i powinno być zapisane jako `("echo " "date") | getline' jeśli chcemy, by program był przenośny na inne implementacje awk. (Zdarza się, że gawk pojmuje to prawidłowo, ale nie powinieneś na tym polegać. Tak czy owak, nawiasy ułatwiają czytanie.)

5.8.7. Użycie getline z potoku do zmiennej

Gdy użyjemy `polecenie | getline zmn', wyjście polecenia polecenie przekazywane jest potokiem do getline i w zmienną zmn. Na przykład, poniższy program, wykorzystując narzędzie date, czyta bieżącą datę i czas do zmiennej current_time a następnie ją wypisuje.

awk 'BEGIN {
     "date" | getline current_time
     close("date")
     print "Raport utworzono: " current_time
}'

W tej wersji getline, nie jest zmieniana żadna ze zmiennych wbudowanych. Rekord nie jest dzielony na pola.

5.8.8. Podsumowanie wariantów getline

We wszystkich postaciach getline, nawet tych, gdzie może być aktualizowane $0 i NF, rekord nie będzie sprawdzany ze wszystkimi wzorcami programu awk, tak jak działoby się to, gdyby był on normalnie czytany przez główną pętlę przetwarzania awk. Jednakże nowy rekord jest sprawdzany z ewentualnymi następnymi regułami.

Wiele z implementacji awk ogranicza ilość potoków, jakie program awk może mieć otwarte, do tylko jednego! W gawk nie ma takiego ograniczenia. Można otworzyć tyle potoków, na ile zezwoli stosowany system operacyjny.

Przy korzystaniu z getline (bez przekierowania) wewnątrz reguły BEGIN pojawia się interesujący efekt uboczny. Ponieważ nieprzekierowane getline czyta z plików wiersza poleceń, pierwsze polecenie getline powoduje, że awk nadaje wartość zmiennej FILENAME. Normalnie FILENAME nie posiada wartości wewnątrz reguł BEGIN, ponieważ nie rozpoczęliśmy jeszcze przetwarzania plików z wiersza poleceń (c.k.). (Zob. 8.1.5. Wzorce specjalne BEGIN i END, również zob. 10.2. Zmienne wbudowane niosące informacje.)

Poniższa tabela podsumowuje sześć wariantów getline, podając, które zmienne wbudowane każdy z nich zmienia.

getline
Nadaje wartość $0, NF, FNR i NR.
getline zmn
Nadaje wartość zmn, FNR i NR.
getline < plik
Nadaje wartość $0 i NF.
getline zmn < plik
Nadaje wartość zmn.
polecenie | getline
Nadaje wartość $0 i NF.
polecenie | getline zmn
Nadaje wartość zmn.

 


Przejdź do pierwszej, poprzedniej, następnej, ostatniej sekcji, spisu treści.

 
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.