Home Dokumentacje Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Funkcje definiowane przez użytkownika
11 | 12 | 2019
Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Funkcje definiowane przez użytkownika Drukuj

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

 


 

13. Funkcje definiowane przez użytkownika

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ć.

13.1. Składnia definicji funkcji

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.

13.2. Przykłady definicji funkcji

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)
}

13.3. Wywoływanie funkcji użytkownika

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 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.

13.4. Instrukcja 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.

 
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.