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).
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.
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
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.
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
Ta sekcja jest dość długa; opisuje jedną z najbardziej fundamentalnych
w awk operacji.
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.
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.
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.
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
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 == " "
FS == inny pojedynczy znak
FS == regexp
FS == ""
(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.
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"
RS == inny pojedynczy znak
RS == ""
FS.
Początkowe i końcowe znaki nowej linii w pliku są ignorowane.
RS == regexp
We wszystkich przypadkach gawk przypisuje zmiennej RT tekst
wejściowy, jaki pasował do wartości podanej przez RS.
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.
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.
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.
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ę.
getline z plikuKonstrukcję `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.
getline z pliku do zmiennejKonstrukcja `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'.
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.)
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.
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
$0, NF, FNR i NR.
getline zmn
FNR i NR.
getline < plik
$0 i NF.
getline zmn < plik
polecenie | getline
$0 i NF.
polecenie | getline zmn
Przejdź do pierwszej, poprzedniej, następnej, ostatniej sekcji, spisu treści.