System UNIX wyposażony jest w wiele narzędzi wspomagających pracę użytkowników. AWK jest jednym ze standardowych narzędzi tego systemu, choć implementacje AWK znaleźć można niemal na każdej platformie systemowej. Nazwa AWK pochodzi od inicjałów jego twórców: Alfreda V. Aho
, Petera J. Weinbergera
i Briana W. Kernighana
.
W jednym zdaniu można powiedzieć, że AWK służy do transformacji danych tekstowych. Istotą działania AWK jest przetwarzanie pliku lub plików wejściowych według zadanego zbioru reguł, generując strumień danych wyjściowych, czy też plików wyjściowych.
Każdy program języka AWK składa się z dowolnej liczby par (w przykładach programów fragmenty, które oznaczają pewne pojęcia, a nie konkretne konstrukcje języka oznaczono kursywą, np.:
instrukcja
oznacza każdą instrukcję AWK):
wzorzec { akcja }
Wzorzec
jest wyrażeniem logicznym, które może być prawdziwe (wówczas wykonywana jest
akcja
) lub fałszywe (
akcja
nie jest wykonywana). Akcja jest
zawsze zawarta pomiędzy parą nawiasów
{
i
}
.
AWK może być wywołany na wiele sposobów. Jeżeli program jest krótki, to najprościej jest umieścić go pomiędzy znakami
pojedynczego cudzysłowa w linii poleceń, w następujący sposób (w systemach DOS/MS Windows
zamiast cudzysłowa pojedynczego należy użyć cudzysłowa maszynowego
"
):
awk 'program
' plik1
plik2
...
Kiedy program jest długi wygodniejsze jest jego umieszczenie w oddzielnym pliku; w tym wypadku uruchomienie programu wygląda następująco (
program
oznacza nazwę pliku zawierającego program):
awk -f program
plik1
plik2
...
Program w języku AWK może zawierać wiele par
wzorzec
{ akcja
}
. AWK czyta po kolei wiersze z
pliku1
,
pliku2
itd. dla wszystkich plików, których nazwy podano w linii poleceń. Pliki te są modyfikowane według programu z pliku
program
, tj. dla każdego wiersza z każdego z plików wejściowych obliczane są kolejne wzorce (w kolejności ich występowania w programie) i wykonywane akcje. Przykład
1 (Usuwanie pustych wierszy) pokazuje program wykorzystujący 2 wzorce. Uwaga: Przykładowe programy mogą zawierać konstrukcje w danym momencie jeszcze nie
omówione. Jeżeli coś jest niezrozumiałe, czytaj dalej, a po lekturze całego tekstu wróć do tego miejsca -- wszystko powinno być jasne. Uwaga 2: Przedstawione przykłady programów są gotowe do uruchomienia w systemach Unix/Linux natomiast w systemach DOS/MS Windows wymagają czasami modyfikacji, np. zamiany znaków
'
na
"
czy zastąpienia skryptów shellowych odpowiednimi plikami
.bat
.
Przed przedstawieniem bardziej szczegółowych informacji o języku wymienimy kilka podstawowych reguł składni AWK:
- kolejne pary ,,
wzorzec
{ akcja
}
'' muszą być oddzielone średnikami lub znakami nowego wiersza;
- akcje mogą składać się z wielu poleceń, które muszą być oddzielone średnikami lub znakami nowego wiersza;
- wzorzec lub akcja może zostać pominięty. W wypadku braku wzorca akcja zostaje wykonana dla każdego wiersza pliku wejściowego. Jeżeli pominiemy akcję, to AWK zastosuje akcję domyślną, jaką jest wydrukowanie wiersza z pliku wejściowego (czyli
{ print $0 }
w składni AWK).
Przykład 1. Usuwanie pustych wierszy
Poniższy program przepisuje plik wejściowy zastępując kolejne puste wiersze, jednym pustym wierszem (autor: Nelson H. F. Beebe).
NF == 0 { nb++ }
NF > 0 { if (nb > 0) print ""; nb = 0; print $0; }
Uwagi
Ponieważ zarówno
wzorzec
jak i
akcja
są opcjonalne, to stosowanie następującego stylu programowania:
wzorzec
{akcja }
jest błędem, gdyż powyższy zapis jest interpretowany jako dwie pary wzorzec-akcja. Pierwszy wiersz jest interpretowany jako
wzorzec
nie posiadający jawnie wyspecyfikowanej
akcji
, co oznacza wydrukowanie wszystkich wierszy z pliku wejściowego, dla których wartością logiczną
wzorca
jest prawda. Trzy następne wiersze są natomiast interpretowane jako
akcja
bez jawnie podanego wzorca, co powoduje, że będzie ona wykonywana
dla każdego kolejnego wiersza z pliku wejściowego. Jeżeli ktoś lubi tego typu styl pisania programów, to musi umieścić otwierający nawias
{
w jednym wierszu z odpowiadającym mu
wzorcem
.
Jest wiele interpretatorów AWK. W systemach uniksopodobnych są dostarczane razem z systemem. Istnieją też wersje ogólnodostępne, takie jak:
gawk -- firmowany przez
Free Software Foundationczy
mawk Michaela Brennana
. Różne implementacje AWK
nie są w 100% kompatybilne ze sobą a ponadto wiele z nich posiada rozszerzenia w stosunku do standardu (za standard przyjmujemy opis z
[AhoetAll]). W niniejszym tekście przedstawiono standard AWK i rozszerzenia interpretatora gawk w wersji 3.*.
Doświadczenia autora wskazują, że komercyjne implementacje AWK
są gorsze niż gawk. Przykładowo maksymalna liczba pól w rekordzie albo maksymalna długość rekordu w znakach może być śmiesznie mała (np. 99 pól w rekordzie). Z tego względu zachęcam do zainstalowania i posługiwania się gawk-iem także w systemach, w których znajduje się inna implementacja AWK. Zainstalowanie gawk-a ze źródeł jest w większości systemów uniksowych bardzo proste (naprawdę!). Oczywiście w skład systemu GNU/Linux standardowo wchodzi gawk zatem problem z głowy. Użytkownicy systemów DOS czy Microsoft Windows, których producent oczywiście nie dołącza AWK, muszą samodzielnie skopiować ,,z sieci'' i zaistalować gawk-a (lub mawka). Kopiujemy gotowe pliki wykonywalne ponieważ ich samodzielne kompilowanie nie jest prostą rzeczą w systemie DOS/MS Windows.
Struktura pliku wejściowego
Dla AWK dane wejściowe składają się z
rekordów, które rozdzielone są separatorami
RS
. Standardowo rekordem jest cały wiersz, czyli separatorem jest znak końca wiersza.
Rekordy podzielone są na
pola, które rozdzielone są separatorami pól
FS
. Domyślnie separatorami pól są odstępy, tj. znaki spacji i/lub tabulacji. Poniżej zamieszczono zawartość pliku
wina.txt
, którego każdy wiersz zawiera: nazwę wina, symbol kraju-producenta, kolor, smak, cenę w złotych oraz liczbę butelek sprzedanych, na przykład w ostatnim miesiącu:
Chardonnay Lyngrove RPA białe wytrawne 95.00 131
Cabernet Sauvignon Merlot Lyngrove RPA czerwone wytrawne 95.00 58
Baron De France Fra białe wytrawne 25.00 289
Anjou Blanc Chenin Fra białe wytrawne 33.00 392
Anjou D'rose Fra różowe półwytrawne 33.00 207
Anjou Rouge Cabernet Fra czerwone półwytrawne 33.00 266
Bordeaux Graveschateau Saint Galier Fra białe wytrawne 89.00 144
Marquis De Chasse 1996 Fra czerwone wytrawne 55.00 229
Don Kichot Tinto Spa czerwone półwytrawne 25.00 360
Rioja Miralcampo Spa czerwone wytrawne 62.00 210
Rioja Miralcampo Spa białe wytrawne 62.00 179
Sherry Rich Cream Spa czerwone słodkie 109.00 55
Sole D'italia Ita czerwone wytrawne 25.00 666
Valpolicella Ita czerwone wytrawne 45.00 370
Chianti Villa Bellafonte Ita czerwone wytrawne 68.00 131
Asti Spumante Docg Ita białe półsłodkie 51.00 207
Moscato Spumante Vsq Ita białe słodkie 26.00 629
Ponieważ nazwa wina składa się z wielu wyrazów, liczba pól w poszczególnych rekordach jest zmienna, np. pierwszy wiersz zawiera 7 pól, drugi 9 pól, trzeci 8 pól, itd.
RS
i
FS
są zmiennymi, a więc można im nadać wartość. Przykładowo, jeśli zmiennej
FS
nadamy wartość `
;
', to separatorami pól będą znaki `
;
' a nie spacje i tabulatory. Wartością zmiennej
FS
może być dowolne
wyrażenie regularne.
W akcjach i wzorcach do wartości pól można się odwoływać za pomocą zmiennych postaci
$nr-pola
. Tak więc
$1
to pierwsze pole rekordu,
$2
drugie itd.
$0
oznacza cały rekord. Wbudowana zmienna
NF
przechowywuje liczbę pól bieżącego rekordu, stąd
$NF
to zmienna zawierająca zawartość ostatniego pola (w każdym rekordzie).
Przykładowo w pliku
wina.txt
zmienna
$NF
zawiera wielkość sprzedaży każdego gatunku wina. Dla pierwszego z win
$1
zawiera napis
"Chardonnay"
a
$2
napis
"Lyngrove"
. Dla drugiego wiersza wartością
$1
będzie
"Cabernet"
itd. Ponieważ liczba wyrazów w nazwie wina jest nieustalona, wydawać by się mogło, że dotarcie do odpowiednich informacji w każdym wierszu może być skomplikowane, ale tak nie jest, bo pola można także liczyć od końca. Zapis
$(NF-1)
oznacza pole
przedostatnie,
$(NF-2)
drugie od końca itd. Nawiasy okrągłe są tutaj obowiązkowe, a ich brak zmienia znaczenie takiej konstrukcji, więcej szczegółów jest w punkcie
„Zmienne przechowujące zawartości pól”.
Plik
wina.txt
był jednorodny w tym sensie, że każdy rekord (wiersz) zawierał informację o jednym gatunku wina. Często można mieć do czynienia z plikami, które zawierają różne rekordy, np. poniżej przedstawiono plik
tdf2000.txt
zawierający zestawienie wszystkich podjazdów o nachyleniu większym od 5% na alpejskich etapach wyścigu
Tour de France '2000:
**** Tour de France *** 1/07/2000 -- 23/07/2000 *** 3630km *****
Nazwa góry/przełęczy Dystans Początek Szczyt Różnica Długość
----------------------------------------------------------------
Etap 14: Draguignan--Briancon
Col d'Allos 127.5 1432 2250 818 13.4
Col de Vars 177.5 1401 2109 708 10.4
Col d'Izoard 249.5 1345 2361 1016 14.1
Etap 15: Briancon--Courchevel
Col du Galibier 33.0 2065 2645 580 8.4
Col de la Madeleine 110.5 456 2000 1544 19.3
Monte de Courchevel 173.5 602 2004 1402 17.3
Etap 16: Courchevel--Morzine
Col des Saisies 80.0 668 1650 982 15.1
Col des Aravis 106.5 973 1498 525 8.2
Col de la Colombiere 131.0 922 1618 696 11.6
Col de Chatillon 158.0 478 733 255 4.9
Col de Joux-Plane 196.5 692 1700 1008 12.0
W tym przypadku w pliku znajdują się następujące grupy rekordów: nagłówek (wiersze 1--3), określające etap, zawierające dane o każdej górze (nazwa, dystans od startu w kilometrach, wysokość w metrach n.p.m. podnóża i szczytu, różnica poziomów oraz długość podjazdu w kilometrach) i puste wiersze separujące.
Do plików
wina.txt
oraz
tdf2000.txt
będziemy często wracać w przykładach zamieszczonych w dalszej części tekstu.
Za pomocą mechanizmu znaków
#!
umieszczonych jako dwa pierwsze znaki w pliku możliwe jest uruchamianie programów AWK-owych, tak jakby były programami wykonywalnymi (w systemach uniksowych, nie w DOS/MS Windows!). Jeżeli przykładowo umieścimy w pliku
pr10
następujący kod:
#!/usr/bin/awk -f
NR <= 10
to, po nadaniu plikowi prawa do wykonywania (za pomocą
chmod), możemy uruchamiać program pisząc po prostu:
pr10 plik
. (Program drukuje pierwsze 10 wierszy
pliku
.)
Wzorce służą do wyznaczenia tych wierszy tekstu, dla których wykonane mają być odpowiednie akcje. W ogólnym wypadku wzorzec może być kombinacją wyrażeń logicznych i wyrażeń regularnych. Ponieważ wzorce są wyrażeniami logicznymi, dozwolone są operatory logiczne:
&&
,
||
,
!
oraz nawiasy. Istnieją dwa specjalne wzorce o nazwie
BEGIN
i
END
. Oto ogólna specyfikacja wzorców:
Wzorce
BEGIN{akcja
}
akcja
jest wykonywana przed otwarciem pliku wejściowego.
END{akcja
}
akcja
jest wykonywana po zamknięciu pliku (plików) wejściowego.
wyrażenie
{akcja
}
akcja
jest wykonywana za każdym razem gdy wartość wyrażenia
jest równa prawda, tj. jest niezerowa (dla wyrażeń numerycznych) lub niepusta (dla napisów).
/wyrażenie-regularne
/{akcja
}
akcja
jest wykonywana za każdym razem gdy wiersz z pliku wejściowego zawiera ciąg znaków pasujący do wyrażenia-regularnego
.
wzorzec-złożony
{akcja
}
- wzorzec złożony to kombinacja logiczna dowolnych warunków. Można stosować operatory
&&
(koniunkcja), ||
(alternatywa), !
(negacja) oraz nawiasy. Por. punkt „Wzorzec złożony”.
wzorzec1
, wzorzec2
{akcja
}
akcja
jest wykonywana dla wszystkich wierszy od wiersza zawierającego wzorzec1
do wiersza zawierającego wzorzec2
(łącznie z tymi wierszami). Wzorzec
oznacza wyrażenie
bądźwyrażenie-regularne
.
Wzorce
BEGIN
i
END
nie mogą być częścią wzorca złożonego. Podobnie częścią wzorca złożonego nie może być wzorzec z przecinkiem.
Wzorzec
BEGIN
nie pasuje do żadnego wiersza z pliku wejściowego, a odpowiadająca mu akcja jest wykonywana przed przeczytaniem przez AWK pierwszego znaku z tego pliku. Podobnie instrukcje wzorca
END
wykonywane są po przeczytaniu wszystkich znaków pliku wejściowego. Możliwe jest umieszczenie wielu wzorców
BEGIN
i
END
w programie AWK-owym; są one wtedy wykonywane po kolei. Zwyczajowo wzorce
BEGIN
są umieszczane na początku a
END
na końcu pliku.
Jednym z najczęstszych sposobów użycia
BEGIN
jest zmiana domyślnego sposobu w jaki AWK dzieli wiersze z czytanego pliku na pola. Wbudowana zmienna
FS
definiuje napis-separator pól w rekordzie. Domyślnie pola oddzielone są znakami spacji lub/i tabulacji (
FS=" "
). Przy uruchomieniu programu zawierającego tylko wzorce
BEGIN
AWK nie oczekuje w linii poleceń nazwy żadnego pliku wejściowego, por. przykład
17 (Wyświetlanie dużych plików).
Wzorcami mogą być wyrażenia arytmetyczne lub napisowe. Odpowiednia
akcja
jest wykonywana za każdym razem gdy takie
wyrażenie
ma wartość różną od zera lub od napisu pustego. Przykładowo:
$(NF-4) == "Fra" { print $0 } # Wydrukuj wina francuskie
w powyższym wierszu, który jest kompletnym programem AWK-owym, wyrażeniem we wzorcu jest porównanie czwartego pola od końca wiersza z napisem
"Fra"
. Jeżeli napisy są identyczne to wartością wyrażenia jest prawda i AWK wykonuje akcję -- drukuje cały wiersz. Zgodnie z tym co już powiedziano, tego typu akcja jest wykonywana domyśnie -- jeżeli nie podano innej, zatem program można zapisać jeszcze krócej:
$(NF-4) == "Fra" # Wydrukuj wina francuskie
Przykładem wzorca zawierającego wyrażenie arytmetyczne może być wydrukowanie tych gatunków win, na których obrót był większy od 10 tys. zł:
$NF * $(NF-1) > 10000 # Wydrukuj najczęściej kupowane
Uwagi
Znak
#
rozpoczyna komentarz
-- wszystko od znaku
#
do końca wiersza jest przez AWK ignorowane.
Wzorzec regularny
to wyrażenie regularne ujęte w parę znaków
/
. Podstawowe sposoby użycia wzorca regularnego to:
/r
/
- Pasuje do bieżącego wiersza z pliku wejściowego jeżeli zawiera ona podnapis pasujący do wyrażenia regularnego
r
.
wyrażenie
~ /r
/
- Pasuje do napisu będącego wartością
wyrażenia
jeżeli zawiera on podnapis pasujący do wyrażenia regularnego r
. Zapis /r
/
jest równoważny formie $0 ~ /r
/
.
wyrażenie
!~ /r
/
- Pasuje do napisu będącego wartością
wyrażenia
jeżeli nie zawiera on podnapisu pasującego do wyrażenia regularnego r
.
Wyrażenia regularne mogą być pomocne w rozwiązaniu problemu wydrukowania win wytrawnych i półwytrawnych:
$(NF-2) ~ /wytrawne/ # wydrukuj coś wytrawnego
AWK wydrukuje każdy wiersz, którego trzecie od końca pole zawiera napis
wytrawne
. W tym przykładzie zysk jest niewielki, zamiast
$(NF-2) ~/wytrawne/
można zapisać:
$(NF-2) == "wytrawne" # wydrukuj wytrawne
$(NF-2) == "półwytrawne" # ... i półwytrawne
ale w ogólnym przypadku wyrażenia regularne potrafią znakomicie ułatwić pracę.
Wzorzec złożony
to wyrażenie złożone z wzorców i operatorów logicznych
||
,
&&
,
!
. Wzorzec złożony pasuje do bieżącego wiersza z pliku wejściowego jeżeli wartością wyrażenia jest
prawda (czyli jest niezerowa lub niepusta). Poniższy przykład:
$(NF-2) == "wytrawne" || $(NF-2) == "półwytrawne" # coś wytrawnego
pokazuje wzorzec złożony i jednocześnie potwierdza, że najlepiej do rozwiązania problemu drukowania win wytrawnych korzystać z wyrażeń regularnych.
Inny przykład wzorca złożonego pozwoli nam rozwiązać problem wydrukowania win francuskich, na których obrót był większy od 10 tys. zł:
$(NF-4) == "Fra" && $(NF-1) * $NF > 10000
Pasuje
do wszystkich wierszy, od wiersza pasującego do
wzorca1
do wiersza pasującego do
wzorca2
(łącznie z tymi wierszami). Jeżeli w pliku po raz kolejny pojawi się
wzorzec1
, to znowu pasują wszystkie wiersze aż do napotkania
wzorca2
. Jeżeli AWK nie znajdzie
wzorca2
, to pasują wszystkie wiersze aż do końca pliku. Jeżeli w pliku nie ma
wzorca1
, to nie pasuje żaden wiersz z tego pliku. Przykładowo wykonanie poniższego programu spowoduje wydrukowanie wierszy o numerach od 4 do 14 z pliku
tdf2000.txt
:
$3 ~ /Briancon/, $3 ~ /Morzine/
Jeżeli wykonamy następujący program:
$3 ~ /Draguignan/, $3 ~ /Briancon/
to powstaje pytanie czy na wydruku otrzymamy tylko
jeden wiersz (zawiera
"Draguignan"
i jednocześnie zawiera
"Briancon"
) czy też
sześć wierszy (od 4 do 9; dziewiąty też zawiera słowo
"Briancon"
) z pliku
tdf2000.txt
? Otóż AWK po sprawdzeniu, że wiersz pasuje do
wzorca1
sprawdza
ten sam wiersz, czy aby nie pasuje on do
wzorca2
, co powoduje, że w takim wypadku drukowany jest tylko ten wiersz. Gdyby AWK działał tak jak program
sed, tj. po dopasowaniu wiersza do
wzorca1
, dopasowywał
wzorzec2
do następnego i kolejnych wierszy wtedy na wydruku pojawiłoby się 6 wierszy.
Wyrażenia regularne
to wyrażenia umożliwiające specyfikowanie
klas napisów. O napisie należącym do tej klasy mówimy, że
pasuje do wyrażenia regularnego. Wyrażenia regularne są konstruowane z następujących elementów: ,,normalnych znaków'' (wszystkie litery, cyfry, większość pozostałych znaków) oraz metaznaków
\
,
^
,
$
,
.
,
[
,
]
,
|
,
(
,
)
,
*
,
+
,
?
. Poniższa tabela przedstawia poszczególne elementy wyrażeń regularnych, według malejącej kolejności wykonywania:
Tabela 1. Wyrażenia regularne
Wyrażenie | Znaczenie |
(r ) | r (nawiasy służą do grupowania wyrażeń) |
c | znak nie będący metaznakiem |
\c | znak sterujący albo znak/metaznak c |
^ | początek napisu |
$ | koniec napisu |
. | dowolny znak |
[ab...] | dowolny ze znaków a , b ... |
[^ab...] | dowolny ze znaków oprócz a , b ... |
r *
| zero lub więcej powtórzeń r |
r +
| jedno lub więcej powtórzenie r |
r ?
| zero lub jedno powtórzenie r |
r1 |r2
| r1 lub r2 (r oznacza wyrażenie regularne) |
Do grupy znaków wewnątrz nawiasów klamrowych pasuje jeden dowolny znak z tej grupy. Wewnątrz nawiasów klamrowych
wszystkie znaki oprócz
\
,
-
i
^
tracą swoje metaznaczenie. Przykładowo:
[...]
oznacza trzy kropki a nie trzy dowolne znaki.
Zapis
[a-z]
oznacza zakres czyli jeden znak od
a
do
z
, przy czym obowiązuje kolejności kodów ASCII. Zatem specyfikacja
[0-9]
jest równoważna
[0123456789]
, zaś
[A-Da-d]
oznacza
[ABCDabcd]
. Jeżeli znak
-
jest pierwszym znakiem w grupie, wtedy jest traktowany literalnie, tj.
[-+]
oznacza albo minus albo plus podczas gdy
[+-]
jest błędem -- AWK oczekuje końca zakresu znaków.
Dopełnieniem grupy lub zakresu znaków jest grupa lub zakres poprzedzona znakiem
^
(bezpośrednio po otwierającym nawiasie
[
). Przykładowo specyfikacja
[^0-9]
oznacza jeden dowolny znak
ale nie cyfrę;
[^A-ZĄĆĘŁŃÓŚŹŻ]
dowolny znak nie będący dużą literą. Znak
^
jest traktowany literalnie jeżeli nie rozpoczyna grupy. Na przykład
^[^^]
pasuje do każdego znaku
oprócz znaku
^
na początku napisu.
Nawiasy okrągłe służą do grupowania i -- podobnie jak w wyrażeniach arytmetycznych -- posiadają najwyższy priorytet wykonania. Przykładowo:
/(Ali|ali)(baba|gator)/
pasuje do następujących napisów:
"Alibaba"
,
"Aligator"
,
"alibaba"
, oraz
"aligator"
. Zwróćmy uwagę, że ponieważ operator
|
ma najniższy priorytet wykonywania możemy pisać
(Ali|ali)
a nie
((Ali)|(ali))
.
Znaki sterujące, zapisujemy w konwencji języka C. Są to:
\a
(dzwonek,
alarm),
\b
(znak cofnięcia,
backspace),
\f
(znak końca strony,
form feed),
\n
(przejście do nowego wiersza,
new line),
\r
(
carriage return),
\t
(znak tabulacji). Ponadto znak
\\
oznacza
\
, zaś każdy znak możemy zapisać przy pomocy kodu ósemkowego używając konwencji
\cyfra
cyfra
cyfra
.
Przykład 2. Przykłady wyrażeń regularnych
Do wyrażenia regularnego /^[ \t]*$/
pasują wszystkie napisy składające się tylko ze znaków spacji, tabulacji i napisu pustego. Do /^[^ \t]*$/
pasują wszystkie napisy oprócz składających się ze spacji, znaków tabulacji i pustych. Z kolei do /[+-]?[0-9]+[.]?[0-9]*/
pasują wszystkie liczby rzeczywiste ze znakiem.
AWK
zawsze dopasowuje do wyrażenia regularnego najdłuższy z możliwych napisów, rozpoczynając dopasowywanie od lewej strony, tak szybko jak to jest możliwe. Poszczególne operatory powtórzeń (
+
,
*
,
?
) dopasowują napis tak długo jak to jest możliwe. Przykładowo niech plik zawiera następujący krótki tekst:
Chardonnay Lyngrove
RPA
Jaki napis zostanie dopasowany do wyrażenia regularnego
/<.+>/
? Na pierwszy rzut oka mogłoby się wydawać, że
, ale nie jest to prawdą: dopasowany zostanie cały wiersz, gdyż jest to najdłuższy z możliwych pasujących napisów zaczynających się od
<
a kończących się na
>
.
Rozpatrzmy kolejny przykład. Jaki napis zostanie dopasowany do wyrażenia:
/C*/
? Odpowiedź, że napis rozpoczynający się od
C
w słowie
Chardonnay
do końca wiersza jest błędna. AWK dopasuje się jedynie do napisu o zerowej długości na początku wiersza. Tak się stanie ponieważ 0 powtórzeń
C
jest poprawnym punktem startu a kolejny znak nie jest już dużą literą
C
-- dopasowywanie jest kończone z wynikiem 0
C
.
Przykład 3. Wysokie góry
Rozważmy z kolei jak można z pliku
tdf2000.txt
wydrukować szczyty o różnicy poziomów większej od 1 km. Nie można po prostu napisać
$(NF-1) > 1000
ponieważ nie wszystkie rekordy zawierają w przedostatnim polu różnicę poziomów. Na przykład w pierwszym wierszu przez zupełny przypadek
$(NF-1)
jest równe 3630. Zresztą napotkanie pustego wiersza spowoduje błąd fatalny i przerwanie wykonywania programu ponieważ w AWK
nie wolno odwoływać się do ujemnych numerów pól. Oczywistym rozwiązaniem jest wyspecyfikowanie akcji dla każdego rodzaju rekordu: nagłówka, nagłówka etapu, góry i pustego:
NR==1, /^-+[ \t]*$/ { next } # Pomiń nagłówek,
NF < 1 { next } # puste wiersze
/^Etap[ \t]+[0-9]+:/ { next } # oraz nagłówki etapów
$(NF-1) > 1000 # Wydrukuj podjazd o różnicy > 1000
Wykorzystana w powyższym programie, jeszcze nie omawiana, instrukcja
next
(por. punkt
„Instrukcje sterujące”) powoduje: przerwanie wykonywania programu, wczytaniu następnego rekordu ze strumienia danych wejściowych a następnie rozpoczęcie wykonywania programu od początku, tj. od pierwszej pary wzorzec-akcja.
Kluczowe znaczenie w powyższym programie ma kolejność poszczególnych par wzorzec-akcja, dzięki której wzorzec
$(NF-1) > 1000
,,widzi'' tylko wiersze z danymi o górach, pozostałe zaś są po drodze ,,odcedzane''.
Napisy jako wyrażenia regularne
Zwykle wyrażenia regularne zapisywane są jako ciągi znaków umieszczone pomiędzy znakami ciachów. Możliwe jest też używanie napisów jako wyrażeń regularnych. Wszędzie tam gdzie AWK oczekuje pojawienia się wyrażenia regularnego (jak, np. po prawej stronie operatorów
~
i
!~
) umieszczone tam wyrażenie zostanie przekształcone a następnie zamienione na napis, który będzie interpretowany jako wyrażenie regularne. Przykładowo, poniższy program:
BEGIN {cyfry="^[0-9]+$" }; $0 ~cyfry
wydrukuje wszystkie wiersze, które zawierają wyłącznie liczbę całkowitą (bez znaku).
Ponieważ wyrażenia napisowe mogą być łączone (por. punkt
„Operatory napisowe”), wyrażenie regularne może być konstruowane dynamicznie z części składowych. Przykładowo poniższy program:
BEGIN {znak ="[-+]?"; cyfra="[0-9]+"; liczba = "^" znak cyfra "$"}
$0 ~ liczba
może służyć do wydrukowane wszystkich wierszy z pliku, które zawierają
wyłącznie liczbę całkowitą ze znakiem:
Uwagi
Jeżeli wewnątrz wyrażenia regularnego występuje literalnie jakiś metaznak to należy go poprzedzić
dwoma a nie jednym znakiem
\
. Przykładowo, program:
$0 ~ /^\\*[0-9]+\\*$/
wydrukuje wiersze zawierające liczbę całkowitą bez znaku otoczoną znakiem
*
.
Podstawą składni wyrażeń AWK jest składnia wyrażeń języka C wzbogacona o operacje tekstowe. Elementami wyrażeń są: stałe, zmienne, operatory, funkcje wbudowane i definiowane przez użytkownika oraz elementy tablic asocjacyjnych.
W AWK istnieją tylko dwa typy danych:
liczbowy i
napisowy. Stałe liczbowe zapisujemy jak w C, tj.
3.1415
lub
1.333e-5
, stałe napisowe otacza się znakami
"
. Stałe napisowe mogą zawierać znaki sterujące takie jak
\n
czy
\f
.
W składni AWK wyróżniamy zmienne: wbudowane, zdefiniowane przez użytkownika i przechowujące zawartości pól. Nazwy zmiennych definiowanych przez użytkownika mogą składać się z liter, cyfr i znaku podkreślenia. Pierwszym znakiem nazwy
nie może być cyfra. Nazwy zmiennych wbudowanych składają się wyłącznie z dużych liter alfabetu. Nazwy zmiennych przechowujących zawartości pól zaczynają się od znaku
$
, po którym występuje liczba (ogólnie: wyrażenie).
Zmienne liczbowe przechowują wartości zmiennopozycyjne, przy czym ich dokładność zależna jest od implementacji. Zmienne napisowe przechowują ciągi znaków (napisy). Zmiennych nie deklaruje się. Typ zmiennej określony jest przez kontekst; w razie potrzeby zawsze dokonywana jest odpowiednia konwersja. Zmienna nie zainicjowana ma wartość zero lub
""
(napis pusty).
Interpretacja wyrażeń numerycznych i tekstowych w operacjach logicznych jest następująca:
fałsz odpowiada liczbie 0 i napisowi pustemu
""
, zaś
prawda odpowiada wszystkim innym liczbom i napisom.
Zmienne wbudowane są dokładnie opisane przy okazji omawiania tych aspektów AWK, których dotyczą:
Tabela 2. Zmienne wbudowane
Zmienna | Opis znaczenia |
ARGC | liczba argumentów wywołania programu |
ARGV | tablica argumentów wywołania programu |
ARGIND | indeks w ARGV odpowiadający bieżącemu plikowi |
ENVIRON | tablica zmiennych środowiskowych |
ERRNO | napis z systemowym opisem błędu |
FIELDWIDTHS | specyfikacja długości pól, por. punkt „Pola o ustalonej długości” |
FILENAME | nazwa bieżącego pliku wejściowego |
FNR | numer bieżącego rekordu w bieżącym pliku |
FS | separator pól |
IGNORECASE | przełącznik rozróżniania wysokości liter |
NF | liczba pól w bieżącym rekordzie |
NR | liczba przeczytanych rekordów |
OFMT | format wydruku argumentów numerycznych funkcji print |
OFS | separator pól na wyjściu, por. punkt „Instrukcja print” |
ORS | separator rekordów na wyjściu, por. punkt „Instrukcja print” |
RLENGTH | por. opis funkcji match w punkcie „Napisowe funkcje wbudowane” |
RS | separator rekordów |
RT | napis pasujący do wyrażenia RS , por. punkt „Rekordy” |
RSTART | por. opis funkcji match w punkcie „Napisowe funkcje wbudowane” |
SUBSEP | separator indeksów tablic, por. punkt „Tablice wielowymiarowe” |
ENVIRON
jest tablicą zawierającą wartości zmiennych środowiskowych przy czym indeksami są nazwy zmiennych. Przykładowo:
gawk 'BEGIN {print ENVIRON["HOME"] }'
zawiera np. /home/tomek
.
ERRNO
zawiera napis z systemowym komunikatem o błędzie, jeżeli przy wykonaniu funkcji
getline
lub
close
wystąpi błąd.
IGNORECASE
określa czy AWK rozróżnia duże i małe litery przy porównywaniu napisów i wyrażeń regularnych. Jeżeli
IGNORECASE
jest niezerowe lub niepuste, wtedy operatory
~
,
!~
i funkcje
gensub
,
gsub
,
index
,
match
,
split
oraz
sub
nie rozróżniają dużych i małych liter. Dotyczy to także wartości zmiennych
RS
i
FS
. Począwszy od wersji 3.0, gawk obsługuje normę ISO-8859-1
(Latin-1). Standard ten
nie zawiera jednak większości polskich znaków diakrytycznych.
ARGIND
przechowuje indeks, pod którym w tablicy
ARGV
znajduje się nazwa przeglądanego pliku. Zawsze jest prawdziwa równość
FILENAME == ARGV[ARGIND]
.
Zmienna
FILENAME
zawiera nazwę bieżącego pliku wejściowego. Oznacza to, że w obrębie wzorców
BEGIN
i
END
wartość
FILENAME
jest nieokreślona.
Zmienne przechowujące zawartości pól
Zmienne przechowujące zawartości pól mogą być wykorzystane wewnątrz wyrażeń, można także im nadawać wartości. Jeżeli wartość zmiennej
$0
została zmodyfikowana przez podstawienie lub zamianę (np. funkcją
sub
) to wartości zmiennych
$1
,
$2
, ... oraz zmiennej
NF
są powtórnie wyznaczane. Podobnie jeżeli zmodyfikowano którąkolwiek ze zmiennych
$1
,
$2
, ..., wtedy wartość zmiennej
$0
jest odtwarzana (przy wykorzystaniu wartości zmiennej
OFS
jako separatora pól).
Numery pól mogą być wyznaczane jako wartości wyrażeń. Przykładowo
$(NF-1)
oznacza przedostatnie pole w bieżącym rekordzie (nawiasy są istotne, ponieważ
$NF-1
oznacza wartość pola
$NF
pomniejszoną o 1). Możliwe jest także przypisanie wartości zmiennym odpowiadającym polom nie istniejącym w czytanym pliku. W takim wypadku pole zostanie utworzone i przypisana zostanie odpowiednia wartość. Odwołanie się do pola o numerze ujemnym jest błędem kończącym działanie programu.
Poniższa tabela zawiera zestawienie wszystkich operatorów arytmetycznych.
Tabela 3. Operatory arytmetyczne
Operator | Opis znaczenia |
* | iloczyn |
+ | suma |
- | różnica |
/ | iloraz |
% | modulo |
^ | potęga |
++ | inkrementacja |
-- | dekrementacja |
Operator modulo to reszta z dzielenia całkowitego, tj.
5.1 % 3 == 2.1
ma wartość prawda.
Napisy i zmienne napisowe można łączyć (konkatenować) przy pomocy ,,niewidocznego'' operatora -- po prostu należy umieścić napisy obok siebie. Przykładowo po wykonaniu:
y = "Ali"; z = "gator"; x1 = y "ba" "ba"; x2= y z;
zmienna
x1
ma wartość ,,
Alibaba
''; zmienna
x2
ma wartość ,,
Aligator
''. Oprócz operacji konkatenacji AWK nie ma żadnych innych operatorów napisowych.
Operatory porównywania i operatory logiczne
Zapis i działanie operatorów w AWK w wypadku zmiennych typu liczbowego jest identyczny jak w języku C. Nowością AWK jest to, że mogą być także stosowane do napisów.
Tabela 4. Operatory porównywania
Operator | Opis znaczenia |
== | równe |
!= | różne |
< | mniejsze |
<= | mniejsze lub równe |
> | większe |
>= | większe lub równe |
Napisy są porównywane według kodów ASCII w taki sposób, że najpierw porównywane są pierwsze znaki, potem drugie itd. Przykładowo:
"10"
jest mniejsze od
"9"
. Jeżeli jeden napis jest przedrostkiem drugiego to krótszy napis jest mniejszy od dłuższego, np.
"Ali"
jest mniejsze od
"Alibaba"
.
Tabela 5. Operatory logiczne
Operator | Opis znaczenia |
&& | iloczyn |
|| | suma |
! | zaprzeczenie |
Przykład 4. Wyznaczanie roku przestępnego
Zaimplementujmy funkcję
, zwracającą 1 jeżeli rok jest przestępny, lub 0 dla lat nieprzestępnych. Algorytm cytujemy za
[KR88], s. 121.
function leapyear(year) {
return year %4 == 0 && year % 100 \
!= 0 || year%400 ==0; }
BEGIN {print leapyear(1996), leapyear(1806),
leapyear(1066)}
Wzorzec
BEGIN
jest potrzebny tylko dla testowania funkcji. Jeżeli powyższy kod umieścimy, np. w pliku
lyear.awk
to pisząc
awk -f lyear.awk
otrzymamy na ekranie:
1 0 0
.
Uwagi
Bardzo długa instrukcja może zostać podzielona i zapisana w kilku wierszach. Znakiem kontynuacji jest \
, bezpośrednio przed znakiem końca wiersza (por. drugi wiersz przykładu). Jeżeli wiersz kończy się przecinkiem (por. wiersz przedostatni) to znak kontynuacji jest opcjonalny.
Operatory pasowania do wyrażeń regularnych
W składni AWK są dwa takie operatory
~
oraz
!~
. Umożliwają one dopasowanie zmiennej do wyrażenia regularnego. Przykładowo
$1 ~/Chardonnay/
jest prawdziwe, gdy pierwsze pole zawiera napis
Chardonnay
. Samotnie pojawiające się wyrażenie
/Chardonnay/
jest równoważne
$0 ~ /Chardonnay/
. Operator
!~
pasuje do dopełnienia wyrażenia regularnego, tj.
$1 !~/Chardonnay/
jest prawdziwe gdy
$1
nie zawiera napisu
Chardonnay
.
Przypisanie oznaczane jest w AWK pojedynczym znakiem równości
=
. Podobnie jak w języku C operator ten nadaje zmiennej wartość i zwraca przypisaną wartość, stąd dozwolone są wyrażenia postaci
x = y = 1
lub
(x = y) <= 1
.
Z operatorem przypisania związane są operatory modyfikacji:
+=
,
-=
,
*=
,
/=
,
%=
,
/=
i
^=
. Przykładowo wyrażenie
x += y
jest tożsame z
x = x + y
, wyrażenie
x -= y
jest tożsame z
x = x - y
itd.
Przykład 5. Wykorzystanie AWK do obliczeń
Dla danych z pliku
wina.txt
, obliczyć obrót dla win białych i czerwonych oraz obrót łączny. Zadanie to rozwiązuje następujący program:
#!/usr/bin/awk -f
{obrot = $NF * $(NF-1); oo += obrot }
$(NF - 3) ~ /białe/ {biale += obrot }
$(NF - 3) ~ /czerwone/ {czerwone += obrot }
END { print "Obrót razem:", oo ", w tym: czerwone:",
czerwone ", białe:", biale "."; }
Zwróćmy uwagę na instrukcję
print. Część argumentów jest oddzielona przecinkiem a część nie. Jest to dopuszczalne ponieważ argumenty oddzielone odstępami są konkatenowane (liczby przed konkatenacją są konwertowane do napisów) i ,,z punktu widzenia'' instrukcji
print stanowią jeden napis. Natomiast argumenty oddzielone przecinkami są na wydruku oddzielone odstępem.
Operator warunkowy
?:
posiada następującą składnię:
wyrażenie1 ? wyrażenie2 : wyrażenie3
Najpierw obliczane jest
wyrażenie1
. Jeśli jest ono prawdziwe obliczane jest
wyrażenie2
, w przeciwnym wypadku
wyrażenie3
.
Poniższy program oblicza i drukuje odwrotność pierwszych pól wszystkich rekordów, sprawdzając czy
$1
nie jest równe zeru:
{print $1!=0 ? 1/$1 : "Zero w wierszu", NR;}