Skomplikowane programy awk można często uprościć definiując własne
funkcje. Funkcje definiowane przez użytkownika wywołuje się tak samo
jak wbudowane (zob. 7.13. Wywołania funkcji), ale należy samodzielnie je
zdefiniować by wskazać awk, co powinny robić.
Definicje funkcji mogą wystąpić gdziekolwiek pomiędzy regułami programu
awk. Zatem, ogólna postać programu awk jest poszerzona tak,
by obejmowała sekwencje reguł oraz definicje funkcji użytkownika.
W awk nie ma potrzeby umieszczania definicji funkcji przed wszystkimi
miejscami, gdzie jest wykorzystywana. Jest to spowodowane tym, że awk
czyta cały program zanim rozpocznie wykonywanie jakiejkolwiek jego części.
Definicja funkcji o nazwie nazwa wygląda tak:
function nazwa(lista-parametrów)
{
ciało-funkcji
}
nazwa jest nazwą definiowanej funkcji. Poprawna nazwa funkcji jest
taka jak poprawna nazwa zmiennej: ciąg liter, cyfr i znaków podkreślenia,
nie zaczynający się cyfrą. W obrębie pojedynczego programu awk każda
z poszczególnych nazw może być użyta tylko albo jako zmienna albo
tablica albo funkcja.
lista-parametrów jest listą argumentów funkcji oraz nazw zmiennych lokalnych, oddzielonych przecinkami. Podczas wywołania funkcji nazwy argumentów używane są do przechowywania wartości podanych w wywołaniu. Zmienne lokalne inicjowane są łańcuchem pustym. Funkcja nie może mieć dwu parametrów o tej samej nazwie.
ciało-funkcji składa się z instrukcji awk. Jest najważniejszą
częścią definicji, gdyż stwierdza, co faktycznie powinna robić
funkcja.
Nazwy argumentów istnieją, by dać ciału metodę
rozmawiania o argumentach; zmienne lokalne, by zapewnić mu miejsca
na przechowywanie tymczasowych wartości.
Nazwy argumentów nie są składniowo odróżniane od nazw zmiennych lokalnych. Zamiast tego, liczba argumentów podanych przy wywołaniu funkcji wyznacza ile jest zmiennych argumentowych. Zatem, jeśli podano trzy wartości argumentów, to pierwsze trzy nazwy z listy-parametrów są argumentami, a reszta jest zmiennymi lokalnymi.
W związku z tym, jeśli liczba argumentów we wszystkich wywołaniach funkcji nie jest taka sama, to niektóre z nazw z listy-parametrów mogą w niektórych przypadkach być argumentami, a w innych zmiennymi lokalnymi. Można też ująć to tak, że pominięte argumenty są domyślnie łańcuchami pustymi.
Zwykle pisząc funkcję wiemy, ile nazw zamierzamy wykorzystać na argumenty, a ile jako zmienne lokalne. Zwyczajowo pomiędzy argumentami a zmiennymi lokalnymi umieszcza się dodatkowy odstęp, by udokumentować spodziewany sposób korzystania z funkcji.
Podczas wykonywania ciała funkcji, wartości argumentów i zmiennych lokalnych
ukrywają lub przesłaniają ewentualne zmienne o tych samych nazwach
używane w pozostałej części programu. Przesłonięte zmienne nie są dostępne
w definicji funkcji, gdyż nie ma sposobu ich nazwania dopóki ich nazwy
są zabrane dla zmiennych lokalnych. Do wszystkich innych zmiennych
użytych w programie awk w ciele funkcji można normalnie się odwoływać
i nadawać im wartości.
Argumenty i zmienne lokalne trwają tylko dopóty, dopóki wykonywane jest ciało funkcji. Zaraz po jej zakończeniu można ponownie sięgać do zmiennych, które były przesłonięte podczas działania funkcji.
Ciało funkcji może zawierać wyrażenia, które wywołują funkcje. Mogą one nawet wywoływać tę funkcję, albo wprost albo za pośrednictwem innej funkcji. Gdy się tak dzieje, mówimy, że funkcja jest rekurencyjna.
W wielu implementacjach awk, łącznie z gawk,
słowo kluczowe function można skrócić
do func. Jednak POSIX podaje tylko stosowanie słowa
kluczowego function. Ma to faktycznie pewne praktyczne
implikacje. Jeśli gawk jest w trybie zgodności z POSIX
(zob. 14.1. Opcje wiersza poleceń), to poniższa instrukcja nie
definiuje funkcji:
func foo() { a = sqrt($1) ; print a }
Zamiast tego, definiuje regułę, która, dla każdego rekordu, skleja wartość
zmiennej `func' z wartością zwracaną przez funkcję `foo'.
Jeśli powstały łańcuch jest niepusty, to wykonywana jest zadana akcja.
Raczej nie tego chciano. (awk przyjmuje to wejście jako składniowo
poprawne, gdyż funkcje mogą być użyte przed ich zdefiniowaniem w programie.)
Dla zapewnienia przenośności programów awk należy przy
definiowaniu funkcji zawsze stosować słowo kluczowe function.
Oto przykład funkcji użytkownika, o nazwie myprint, pobierającej
liczbę i wypisującej ją w konkretnym formacie.
function myprint(num)
{
printf "%6.3g\n", num
}
Dla ilustracji, poniżej podamy regułę awk korzystającą z naszej
funkcji myprint:
$3 > 0 { myprint($3) }
Program ten wypisuje, w naszym specjalnym formacie, wszystkie trzecie pola wejścia zawierające liczbę dodatnią. Stąd też, przy danych:
1.2 3.4 5.6 7.8 9.10 11.12 -13.14 15.16 17.18 19.20 21.22 23.24
program, wykorzystując naszą funkcję formatującą wyniki, wypisze:
5.6 21.2
Poniższa funkcja usuwa wszystkie elementy z tablicy.
function delarray(a, i)
{
for (i in a)
delete a[i]
}
Przy pracy z tablicami często konieczne jest usunięcie wszystkich elementów
tablicy i ponowne rozpoczęcie z nową listą elementów
(zob. 11.6. Instrukcja delete).
Zamiast konieczności powtarzania tej pętli w każdym miejscu programu,
w którym potrzebujemy wyczyścić tablicę, nasz program może po prostu
wywoływać delarray.
(Gwarantuje to przenośność. Wykorzystanie `delete tablica'
do usunięcia zawartości całej tablicy jest niestandardowym rozszerzeniem.)
A oto przykład funkcji rekurencyjnej. Jako parametr wejściowy pobiera łańcuch, a zwraca ten łańcuch w odwróconej kolejności.
function rev(str, start)
{
if (start == 0)
return ""
return (substr(str, start, 1) rev(str, start - 1))
}
Jeżeli funkcja ta znajduje się w pliku o nazwie `rev.awk', możemy przetestować ją tak:
$ echo "Nie panikuj!" |
> gawk --source '{ print rev($0, length($0)) }' -f rev.awk
-| !jukinap eiN
Oto przykład wykorzystujący wbudowaną funkcję strftime.
(Zob. 12.5. Funkcje obsługi znaczników czasu,
gdzie bliżej opisano strftime.)
Funkcja ctime z C pobiera znacznik czasu i zwraca go
w łańcuchu, sformatowanym w dobrze znany sposób. Oto jej wersja awk:
# ctime.awk
#
# wersja awk funkcji ctime(3) z C
function ctime(ts, format)
{
format = "%a %b %d %H:%M:%S %Z %Y"
if (ts == 0)
ts = systime() # użyj czasu bieżącego jako domyślnego
return strftime(format, ts)
}
Wywołanie funkcji oznacza spowodowanie uruchomienia funkcji i wykonania jej zadania. Wywołanie funkcji jest wyrażeniem, a jego wartością jest wartość zwracana przez funkcję.
Wywołanie funkcje składa się z nazwy funkcji z następującymi po niej
w nawiasach argumentami. W miejscu argumentów wywołania wpisuje się
wyrażenia awk. Wyrażenia te są obliczane za każdym razem, gdy
wykonywane jest wywołanie, a ich wartości stanowią parametry aktualne.
Na przykład, oto wywołanie foo z trzema argumentami (pierwszy
będący złożeniem łańcuchów):
foo(x y, "strata", 4 * z)
Uwaga: nie dopuszcza się białych znaków (spacji i tabulacji) między
nazwą funkcji a nawiasem otwierającym listy argumentów. Jeśli przez pomyłkę
napisalibyśmy biały znak, awk mógłby pomyśleć, że mamy zamiar
konkatenować zmienną z wyrażeniem w nawiasach. Zauważy jednak, że użyliśmy
nazwy funkcji a nie nazwy zmiennej, i zgłosi błąd.
Gdy funkcja jest wołana, otrzymuje kopię wartości swoich argumentów. Jest to znane jako wywołanie przez wartość. Wywołujący jako wyrażenia argumentowego może użyć zmiennej, ale wywołująca funkcja tego nie wie: wie tylko, jaką wartość miał argument. Na przykład, jeśli napiszemy taki kod:
foo = "bar" z = myfunc(foo)
to nie powinniśmy myśleć o argumencie myfunc, że jest "zmienną
foo". Zamiast tego, należy traktować go jako wartość łańcuchową,
"bar".
Jeżeli funkcja myfunc zmienia wartości swoich zmiennych
lokalnych, to nie ma to żadnego wpływu na inne zmienne. Zatem, jeśli
myfunc robi tak:
function myfunc(str)
{
print str
str = "zzz"
print str
}
aby zmienić swoją pierwszą zmienną argumentową str, to nie
zmienia to wartości foo w miejscu wywołania. Rola zmiennej
foo w wywoływaniu myfunc skończyła się w chwili, gdy została
obliczona jej wartość, "bar". Jeżeli str istnieje także poza
myfunc, to ciało funkcji nie może zmienić tej zewnętrznej wartości,
gdyż podczas wykonywania myfunc jest ona przesłonięta i nie może być
stąd widziana ani zmieniana.
Jednak, gdy parametrami funkcji są tablice, nie są one kopiowane. Zamiast tego, do bezpośredniej na niej manipulacji udostępniana jest funkcji sama tablica. Zwykle nazywane jest to wywołaniem przez odwołanie. Zmiany dokonane na parametrze tablicowym wewnątrz ciała funkcji są widoczne poza tą funkcją. Może to być bardzo niebezpieczne jeżeli nie uważa się na to, co się robi. Na przykład:
function zmiento(tabl, ind, nwart)
{
tabl[ind] = nwart
}
BEGIN {
a[1] = 1; a[2] = 2; a[3] = 3
zmiento(a, 2, "dwa")
printf "a[1] = %s, a[2] = %s, a[3] = %s\n",
a[1], a[2], a[3]
}
Program ten wypisuje `a[1] = 1, a[2] = dwa, a[3] = 3', ponieważ
zmiento umieszcza "dwa" w drugim elemencie a.
Niektóre implementacje awk pozwalają na wywoływanie funkcji, która
nie została zdefiniowana, i zgłaszają problem tylko w czasie wykonania, gdy
program faktycznie usiłuje wywołać funkcję. Na przykład:
BEGIN {
if (0)
foo()
else
bar()
}
function bar() { ... }
# zauważ, że `foo' nie jest zdefiniowane
Ponieważ instrukcja `if' nigdy nie będzie prawdziwa, to, że nie
zdefiniowano foo, nie stanowi rzeczywistego kłopotu. Zwykle jednak
jeśli program wywołuje niezdefiniowaną funkcję, to jest to problem.
Jeżeli podano opcję `--lint'
(zob. 14.1. Opcje wiersza poleceń),
gawk będzie zawiadamiał o wywołaniach niezdefiniowanych funkcji.
Niektóre implementacje awk generują błąd wykonania jeśli użyje się
instrukcji next
(zob. 9.7. Instrukcja next)
wewnątrz funkcji definiowanej przez użytkownika.
gawk nie ma tego problemu.
return
Ciało funkcji definiowanej przez użytkownika może zawierać instrukcję
return. Zwraca ona sterowanie do dalszej części programu awk.
Może być też wykorzystana do zwracania wartości do wykorzystania w dalszej
części programu. Wygląda tak:
return [wyrażenie]
Część wyrażenie jest opcjonalna. Jeśli zostanie pominięta, to zwracana wartość jest niezdefiniowana i, z tego powodu, nieprzewidywalna.
Na końcu każdej definicji funkcji zakłada się występowanie instrukcji
return bez wyrażenia zwracającego wartość. Zatem jeśli sterowanie
osiągnie koniec ciała funkcji, to funkcja zwraca nieprzewidywalną wartość.
awk nie będzie ostrzegał o użyciu przez nas wartości zwracanej
przez taką funkcję.
Czasami, chcemy napisać funkcję, gdyż potrzebujemy tego, co ona robi, a nie
co zwraca. Funkcja taka odpowiada funkcji void znanej z C, czy
procedurze (procedure) Pascala. Zatem, niezwracanie wartości może
być właściwe; powinno się po prostu pamiętać, że jeżeli wykorzystujemy
wartość takiej funkcji, to robimy to na własne ryzyko.
Oto przykład funkcji użytkownika zwracającej wartość największej liczby występującej wśród elementów tablicy:
function maxelt(vec, i, ret)
{
for (i in vec) {
if (ret == "" || vec[i] > ret)
ret = vec[i]
}
return ret
}
Wywołujemy maxelt z jednym argumentem, będącym nazwą tablicy.
Zmiennych lokalnych i i ret nie zaplanowano jako argumentów;
mimo, że nic nie może nas powstrzymać od przekazania do maxelt dwóch
lub trzech argumentów, to wyniki mogą być dziwne. Dodatkowe odstępy przed
i w liście parametrów funkcji wskazują, że nie zakłada się by
i i ret były argumentami. Jest to konwencja, której powinno
się przestrzegać przy definiowaniu własnych funkcji.
Oto program korzystający z naszej funkcji maxelt. Wczytuje on
tablicę, wywołuje maxelt i zgłasza największą liczbę w tej tablicy:
awk '
function maxelt(vec, i, ret)
{
for (i in vec) {
if (ret == "" || vec[i] > ret)
ret = vec[i]
}
return ret
}
# wczytaj wszystkie pola każdego rekordu do nums.
{
for(i = 1; i <= NF; i++)
nums[NR, i] = $i
}
END {
print maxelt(nums)
}'
Otrzymując poniższe dane wejściowe:
1 5 23 8 16 44 3 5 2 8 26 256 291 1396 2962 100 -6 467 998 1101 99385 11 0 225
program nasz powie (zgodnie z oczekiwaniami), że największą liczbą w naszej
tablicy jest 99385.
Przejdź do pierwszej, poprzedniej, następnej, ostatniej sekcji, spisu treści.