Home Dokumentacje Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Biblioteczka funkcji awk
14 | 12 | 2019
Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Biblioteczka funkcji awk Drukuj

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

 


 

15. Biblioteczka funkcji awk

W niniejszym rozdziale pokazano biblioteczkę przydatnych funkcji awk. Funkcje te wykorzystano w pokazanych dalej przykładowych programach (zob. 16. Praktyczne programy awk). Funkcje przedstawiono kolejno od prostych do skomplikowanych.

16.2.7. Wydzielanie programów z plików źródłowych Texinfo, pokazuje program, który można wykorzystać do wydzielenia kodu źródłowego omawianych funkcji bibliotecznych i programów ze źródła Texinfo tej książki. (Zrobiono to już jako część dystrybucji gawk.)

Jeżeli napisałeś jakąś przydatną funkcję awk ogólnego zastosowania i chciałbyś wnieść ją do kolejnego wydania książki, skontaktuj się proszę z autorem. Zob. B.7. Zgłaszanie problemów i błędów, gdzie opisano, jak to robić. Nie wysyłaj samego kodu, gdyż będzie wymagana twoja zgoda albo na umieszczenie go w zasobach ogólnie dostępnych (public domain), albo opublikowanie go na licencji GPL (zob. 18. Powszechna Licencja Publiczna GNU), albo przypisanie jego praw autorskich Fundacji (Free Software Foundation).

15.1. Symulowanie cech specyficznych dla gawk

Programy w tym rozdziale i w 16. Praktyczne programy awk, swobodnie korzystają z cech specyficznych dla gawk. Ta sekcja krótko omawia, w jaki sposób można je przepisać dla innych implementacji awk.

Komunikaty diagnostyczne wysyłane są na `/dev/stderr'. Należy użyć `| "cat 1>&2"' zamiast `> "/dev/stderr"', jeśli dany system nie posiada `/dev/stderr', lub jeżeli nie można użyć gawk.

Wiele z programów korzysta z nextfile (zob. 9.8. Instrukcja nextfile), do pominięcia ewentualnych pozostałych danych z pliku wejściowego. 15.2. Implementacja nextfile jako funkcji, pokazuje jak napisać funkcję, która robi to samo.

Na koniec, niektóre z tych programów ignorują rozróżnianie dużych i małych liter w wejściu. Robią to przypisując jeden do IGNORECASE. Można osiągnąć ten sam skutek dodając następującą regułę na początku programu:

# ignorujemy wielkość znaków
{ $0 = tolower($0) }

Należy też sprawdzić czy wszystkie stałe wyrażenia regularne i stałe łańcuchowe wykorzystywane w porównaniach posługują się tylko małymi literami.

15.2. Implementacja nextfile jako funkcji

Instrukcja nextfile przedstawiona w 9.8. Instrukcja nextfile, jest rozszerzeniem specyficznym dla gawk. Nie jest dostępna w innych implementacjach awk. Ta sekcja pokazuje dwie wersje funkcji nextfile, które można wykorzystać do symulowania instrukcji nextfile z gawk jeśli nie można użyć samego gawk.

Oto pierwsza próba napisania funkcji nextfile.

# nextfile --- pomiń pozostałe rekordy bieżącego pliku

# to powinno być przeczytane przed "głównym" programem awk

function nextfile()    { _porzuc_ = FILENAME; next }

_porzuc_ == FILENAME  { next }

Plik ten powinien był dołączony przed głównym programem, gdyż podaje on regułę, która musi być wykonana wcześniej. Reguła porównuje nazwę bieżącego pliku danych (która jest zawsze w zmiennej FILENAME) ze zmienną prywatną o nazwie _porzuc_. Jeśli nazwa pliku pasuje, to akcja tej reguły wykonuje instrukcję next, by przejść dalej do kolejnego rekordu. (Użycie `_' w nazwie zmiennej jest konwencją. Opisano ją pełniej w 15.13. Nazywanie zmiennych globalnych funkcji bibliotecznych.)

Użycie instrukcji next w praktyce tworzy pętlę czytającą wszystkie rekordy bieżącego pliku danych. Ostatecznie, osiągany jest koniec pliku i otwierany jest nowy plik danych, co zmienia wartość FILENAME. Gdy się to stanie, porównanie _porzuc_ z FILENAME daje wynik negatywny, a wykonywanie jest kontynuowane od pierwszej reguły "faktycznego" programu.

Sama funkcja nextfile po prostu nadaje wartość zmiennej _porzuc_ a następnie wykonuje instrukcję next, by uruchomić pętlę.(20)

Przedstawiona początkowa wersja ma drobną usterkę. Co się stanie jeśli jakiś plik danych wymieniono w wierszu poleceń dwukrotnie, raz za razem, lub nawet z samym tylko przypisaniem zmiennej między oboma wystąpieniami tej samej nazwy pliku?

W takim przypadku, nasz kod przeskoczy przez plik, drugi raz, mimo że powinien zatrzymać się po dojściu do końca pierwszego wystąpienia. Oto druga wersja nextfile, która zaradzi temu kłopotowi.

# nextfile --- pomiń pozostałe rekordy bieżącego pliku
# poprawnie obsługuje kolejne wystąpienia tego samego pliku
# Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 , Public Domain
# May, 1993

# to powinno być przeczytane przed "głównym" programem awk

function nextfile()   { _porzuc_ = FILENAME; next }

_porzuc_ == FILENAME {
      if (FNR == 1)
          _porzuc_ = ""
      else
          next
}

Funkcja nextfile nie zmieniła się. Przypisuje _porzuc_ nazwę bieżącego pliku a następnie wykonuje instrukcję next. Instrukcja ta odczytuje kolejny rekord i zwiększa FNR, tak że FNR na pewno będzie mieć wartość co najmniej dwa. Jeśli jednak nextfile wywołano dla ostatniego rekordu pliku, to awk zamknie bieżący plik danych i przejdzie do następnego. Robiąc to, przypisze FILENAME nazwę nowego pliku i nada FNR początkową wartość jeden. Jeżeli następny plik jest taki sam jak poprzedni, _porzuc_ będzie nadal równe FILENAME. Jednak FNR będzie równe jeden, mówiąc nam, że jest to nowe wystąpienie tego pliku, a nie to, które odczytywaliśmy podczas wykonywania funkcji nextfile. W tym przypadku zmiennej _porzuc_ przywracana jest początkowa wartość łańcucha pustego, tak że następne wywołania tej reguły dadzą wynik negatywny (do następnego razu gdy zostanie wywołana nextfile).

Jeśli FNR nie jest równe jeden, to jesteśmy nadal w pierwotnym pliku danych, a program wykonuje instrukcję next, przeskakując przezeń.

W tym momencie pojawia się istotne pytanie: "Skoro sposób działania nextfile można zapewnić stosując plik biblioteczny, po co wbudowano ją w gawk?" Jest to ważna kwestia. Dodawanie cech z błahych powodów prowadzi do większych, wolniejszych programów, które są trudniejsze w pielęgnacji.

Odpowiedź brzmi: wbudowanie nextfile w gawk daje znaczący przyrost efektywności. Jeżeli funkcja nextfile wykonywana jest na początku dużego pliku danych, awk nadal musi badać cały plik, rozbijając go na rekordy, tylko po to by po nim przeskakiwać. Wbudowane nextfile może po prostu natychmiast zamknąć plik i przejść do następnego, oszczędzając mnóstwa czasu. Jest to szczególnie ważne w awk, gdyż programy awk są na ogół ograniczone wejściem/wyjściem (tj. większość czasu zużywają na dokonywanie operacji wejścia i wyjścia, a nie na wykonywanie obliczeń).

15.3. Asercje

Przy pisaniu dużych programów często przydatna jest możliwość dowiedzenia się, że dany warunek czy zestaw warunków jest prawdziwy. Przed przystąpieniem do konkretnego obliczenia, tworzymy zdanie, stwierdzające, jaki jest oczekiwany stan. Zdanie takie nazywamy "asercją". W języku C dostarczany jest plik nagłówkowy <assert.h> i odpowiadające mu makro assert, które programista może wykorzystać do tworzenia asercji. Jeżeli asercja daje wynik negatywny, to makro assert zapewnia wypisanie komunikatu diagnostycznego opisującego warunek, który powinien być prawdziwy a nie był, a następnie ubija program. W C wykorzystanie assert wygląda tak:

#include <assert.h>

int myfunc(int a, double b)
{
     assert(a <= 5 && b >= 17);
     ...
}

Jeśli asercja nie byłaby spełniona, program wypisałby komunikat podobny do tego:

prog.c:5: assertion failed: a <= 5 && b >= 17

Język ANSI C umożliwia przekształcenie warunku na łańcuch znakowy w celu zamieszczenia go w wypisywanym komunikacie diagnostycznym. Nie jest to możliwe w awk, więc ta funkcja assert wymaga testowanego warunku także w postaci łańcucha.

# assert --- zweryfikuj, czy warunek jest prawdziwy.
#            Jeśli nie, to zakończ.
# Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 , Public Domain # May, 1993

function assert(warunek, lancuch)
{
    if (! warunek) {
        printf("%s:%d: assertion failed: %s\n",
            FILENAME, FNR, lancuch) > "/dev/stderr"
        _assert_exit = 1
        exit 1
    }
}

END {
    if (_assert_exit)
        exit 1
}

Funkcja assert testuje parametr warunek. Jeśli jest on fałszywy, wypisuje na standardowym wyjściu komunikat, do opisania nie spełnionego warunku posługując się parametrem lancuch. Następnie przypisuje zmiennej _assert_exit jeden i wykonuje instrukcję exit. Instrukcja exit skacze do reguły END. Jeśli reguła END stwierdzi, że _assert_exit jest prawdziwe, to natychmiast kończy pracę programu.

Celem reguły END i jej testu jest powstrzymanie innych reguł END przed zadziałaniem. Gdy asercja nie jest spełniona, program powinien bezzwłocznie zakończyć pracę. Jeżeli żadna asercja nie zawiodła, to podczas normalnego uruchomienia reguły END _assert_exit będzie nadal fałszywe, i zostaną wykonane pozostałe reguły END programu. By wszystko to funkcjonowało poprawnie, `assert.awk' musi być pierwszym plikiem źródłowym czytanym przez awk.

Z funkcji assert w programach korzysta się tak:

function myfunc(a, b)
{
     assert(a <= 5 && b >= 17, "a <= 5 && b >= 17")
     ...
}

Jeśli asercja nie będzie spełniona, zobaczymy komunikat typu:

mydata:1357: assertion failed: a <= 5 && b >= 17

Z tą wersją assert jest pewien kłopot, który może być nie do obejścia w standardowym awk. Reguła END jest automatycznie dodawana do programu wywołującego assert. Normalnie, jeśli program składa się wyłącznie z reguły BEGIN, pliki wejściowe i/lub standardowe wejście nie są czytane. Teraz jednak, ponieważ program ma regułę END, awk będzie próbował czytać pliki wejściowe czy standardowe wejście (zob. 8.1.5.1. Akcje początkowe i końcowe), najprawdopodobniej powodując zawieszenie pracy programu, czekającego na dane wejściowe.

15.4. Zaokrąglanie liczb

Sposób, w jaki printf i sprintf (zob. 6.5. Wymyślne wyjście dzięki instrukcji printf) wykonują zaokrąglanie będzie często zależał od procedury C sprintf danego systemu. Na wielu maszynach, zaokrąglanie sprintf jest "nierówne", co znaczy, że, wbrew prostodusznym oczekiwaniom, nie zawsze zaokrągla końcowe `.5' w górę. W zaokrągleniu nierównym `.5' zaokrąglane jest do najbliższej parzystej, zamiast zawsze w górę. Zatem 1.5 zaokrąglane jest do 2, ale 4.5 do 4. W wyniku tego, jeśli stosujemy format wykonujący zaokrąglenia (np., "%.0f"), to powinniśmy sprawdzić, co robi nasz system. Poniższa funkcja wykonuje tradycyjne zaokrąglanie. Może się przydać jeśli printf naszego awk zaokrągla nierówno.

# round --- robi normalne zaokrąglanie
#
# Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 , August, 1996
# Public Domain

function round(x,   wartc, wartb, ulamek)
{
   wartc = int(x)    # część całkowita, int() obcina

   # zobacz, czy jest część ułamkowa
   if (wartc == x)   # bez ułamka
      return x

   if (x < 0) {
      wartb = -x     # wartość bezwzględna
      wartc = int(wartb)
      ulamek = wartb - wartc
      if (ulamek >= .5)
         return int(x) - 1   # -2.5 --> -3
      else
         return int(x)       # -2.3 --> -2
   } else {
      ulamek = x - wartc
      if (ulamek >= .5)
         return wartc + 1
      else
         return wartc
   }
}

# uruchomienie z testem
{ print $0, round($0) }

15.5. Konwersja między znakami a liczbami

Jedna z komercyjnych implementacji awk zawiera funkcję wbudowaną ord, pobierającą znak i zwracającą liczbową wartość tego znaku w zestawie znaków naszej maszyny. Jeżeli łańcuch przekazany ord zawiera więcej niż jeden znak, to używany jest tylko pierwszy.

Odwrotnością tej funkcji jest chr (od funkcji o tej samej nazwie w Pascalu), która pobiera liczbę i zwraca odpowiadający jej znak.

Obie funkcje można ładnie napisać w awk; nie faktycznego powodu, by wbudowywać je w interpreter awk.

# ord.awk --- robi ord i chr
#
# Identyfikatory globalne:
#    _ord_:      wartości numeryczne indeksowane znakami
#    _ord_init:  funkcja inicjująca _ord_
#
# Arnold Robbins
# 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 
# Public Domain
# 16 January, 1992
# 20 July, 1992, revised

BEGIN    { _ord_init() }

function _ord_init(    low, high, i, t)
{
    low = sprintf("%c", 7) # BEL to ascii 7
    if (low == "\a") {    # zwykłe ascii
        low = 0
        high = 127
    } else if (sprintf("%c", 128 + 7) == "\a") {
        # ascii, ze znacznikiem parzystości
        low = 128
        high = 255
    } else {        # ebcdic(!)
        low = 0
        high = 255
    }

    for (i = low; i <= high; i++) {
        t = sprintf("%c", i)
        _ord_[t] = i
    }
}

Warto poświęcić chwilę na pewne wyjaśnienie liczb używanych przez chr. Najważniejszym obecnie stosowanym zestawem znaków jest ASCII. Mimo, że bajt ośmiobitowy może przechować 256 różnych wartości (od zera do 255), ASCII definiuje tylko znaki wykorzystujące wartości od zera do 127.(21) Wiemy o co najmniej jednym producencie komputerów, który stosuje ASCII, ale ze znacznikiem parzystości, co znaczy, że skrajny lewy bit bajtu jest zawsze jedynką. Znaczy to, że na takich systemach znaki mają numeryczne wartości od 128 do 255. Na koniec, wielkie systemy mainframe stosują zestaw znaków EBCDIC, wykorzystujący wszystkie 256 wartości. Mimo, że w niektórych starszych systemach w użytku są inne zestawy znaków, nie warto faktycznie się nimi przejmować.

function ord(str,    c)
{
    # interesuje nas tylko pierwszy znak
    c = substr(str, 1, 1)
    return _ord_[c]
}

function chr(c)
{
    # wymuś numeryczne c przez dodanie 0
    return sprintf("%c", c + 0)
}

#### kod testowy ####
# BEGIN    \
# {
#    for (;;) {
#        printf("podaj znak: ")
#        if (getline zmn <= 0)
#            break
#        printf("ord(%s) = %d\n", zmn, ord(zmn))
#    }
# }

Oczywistym udoskonaleniem tych funkcji byłoby przesunięcie kodu funkcji _ord_init do ciała reguły BEGIN. Napisano go początkowo w ten sposób by ułatwić budowę.

Mamy tu "program testowy" w regule BEGIN, do testowania funkcji. Do czynnego użytkowania został zakomentowany.

15.6. Scalanie tablicy w łańcuch

Przy przetwarzaniu łańcuchów często przydaje się możliwość połączenia wszystkich łańcuchów z jakiejś tablicy w jeden długi łańcuch. Cel ten realizuje poniższa funkcja, join. Jest wykorzystywana dalej w kilku programach użytkowych (zob. 16. Praktyczne programy awk).

Istotny jest dobry projekt funkcji. Nasza funkcja powinna być ogólna, ale też jej zachowanie domyślne powinno być rozsądne. Wywoływana jest z tablicą oraz początkowym i końcowym indeksem elementów, jakie mają zostać połączone. Zakłamy, że elementy tablicy są numeryczne -- rozsądne założenie, gdyż tablica zapewne została utworzona za pomocą split (zob. 12.3. Funkcje wbudowane działające na łańcuchach).

# join.awk --- łączy tablicę w łańcuch
# Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 , Public Domain
# May 1993

function join(tablica, pocz, kon, sep,    wynik, i)
{
    if (sep == "")
       sep = " "
    else if (sep == SUBSEP) # magiczna wartość
       sep = ""
    wynik = tablica[pocz]
    for (i = pocz + 1; i <= kon; i++)
        wynik = wynik sep tablica[i]
    return wynik
}

Dodatkowy, opcjonalny argument jest separatorem, jaki ma być zastosowany przy ponownym łączeniu łańcuchów. Jeśli wywołanie dostarczy niepustą wartość, join skorzysta z niej. Jeśli jej nie poda, to argument będzie mieć wartość pustą. W tym przypadku join jako domyślny separator łańcuchów stosuje pojedynczy odstęp. Jeżeli wartość jest równa SUBSEP, to join złączy łańcuchy bez separatora między nimi. SUBSEP służy jako "magiczna" wartość, wskazująca, że między składowymi łańcuchami nie powinno być rozdzielania.

Byłoby miło, gdyby awk miał operator przypisania dla konkatenacji. Brak jawnego operatora konkatenacji powoduje, że operacje na łańcuchach są trudniejsze niż muszą być faktycznie.

15.7. Konwersja dat na znaczniki czasu

Funkcja systime wbudowana w gawk zwraca bieżący czas jako znacznik czasowy w "sekundach od początku Epoki". Stosując wbudowaną funkcję strftime znacznik czasu można przekształcić na drukowalną datę w niemal nieograniczenie dowolnym formacie. (Więcej o systime i strftime, zob. 12.5. Funkcje obsługi znaczników czasu.)

Interesującym, lecz trudnym problemem jest konwersja czytelnej reprezentacji daty z powrotem na znacznik czasu. Biblioteka C posiada funkcję mktime, która wykonuje podstawowe zadanie, przekształcając na znacznik czasu reprezentację kanoniczną daty.

Na pierwszy rzut oka, wygląda na to, że gawk musiałby mieć funkcję wbudowaną mktime, która byłaby po prostu "punktem zaczepienia" do wersji z języka C. W rzeczywistości jednak mktime można zrealizować w całości w awk.(22)

Oto wersja mktime napisana w awk. Pobiera zwykłą reprezentację daty i czasu i przekształca ją na znacznik czasu.

Przedstawiony kod jest przepleciony prozą. W 16.2.7. Wydzielanie programów z plików źródłowych Texinfo, zobaczymy, w jaki sposób można przetworzyć plik źródłowy Texinfo tej książki, by wydzielić kod do pojedynczego pliku źródłowego.

Program zaczyna się komentarzem opisowym i regułą BEGIN, która inicjuje tablicę _tm_mies. Jest to dwuwymiarowa tablica zawierająca długości poszczególnych miesięcy. Pierwszym indeksem jest zero dla zwykłych lat, zaś jeden dla przestępnych. W obu rodzajach lat wartości dla wszystkich miesięcy, za wyjątkiem lutego, są takie same. Z tego powodu stosujemy przypisanie wielokrotne.

# mktime.awk --- przekształca kanoniczną reprezentację
#                daty na znacznik czasu

# Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 , Public Domain
# May 1993

BEGIN    \
{
    # inicjacja tablicy długości miesięcy
    _tm_mies[0,1] = _tm_mies[1,1] = 31
    _tm_mies[0,2] = 28; _tm_mies[1,2] = 29
    _tm_mies[0,3] = _tm_mies[1,3] = 31
    _tm_mies[0,4] = _tm_mies[1,4] = 30
    _tm_mies[0,5] = _tm_mies[1,5] = 31
    _tm_mies[0,6] = _tm_mies[1,6] = 30
    _tm_mies[0,7] = _tm_mies[1,7] = 31
    _tm_mies[0,8] = _tm_mies[1,8] = 31
    _tm_mies[0,9] = _tm_mies[1,9] = 30
    _tm_mies[0,10] = _tm_mies[1,10] = 31
    _tm_mies[0,11] = _tm_mies[1,11] = 30
    _tm_mies[0,12] = _tm_mies[1,12] = 31
}

Korzyści z łączenia wielu reguł BEGIN (zob. 8.1.5. Wzorce specjalne BEGIN i END) są szczególnie oczywiste przy pisaniu plików bibliotecznych. Funkcje zawarte w bibliotece mogą bez trudności inicjować własne prywatne dane. Mogą też zapewnić akcje porządkowe w prywatnych regułach END.

Następna funkcja jest prosta. Oblicza, czy dany rok jest przestępny czy nie. Jeżeli rok jest podzielny bez reszty przez cztery, ale nie dzieli się przez 100, albo jeśli jest podzielny przez 400, to jest to rok przestępny. Zatem, 1904 był rokiem przestępnym, 1900 nie był, ale 2000 jest przestępny.

# decyduje czy rok jest przestępny
function _tm_isleap(rok,    ret)
{
    ret = (rok % 4 == 0 && rok % 100 != 0) ||
            (rok % 400 == 0)

    return ret
}

Funkcja ta w naszym pliku wykorzystywana jest tylko kilkukrotnie, a jej obliczenia mogłyby zostać wstawione (zapisane in-line) w miejscu, gdzie jest używana. Zrobienie z nich osobnej funkcji ułatwiło początkowe konstruowanie, zapobiega także możliwości popełnienia błędów typograficznych przy powielaniu kodu w wielu miejscach.

Kolejna funkcja jest bardziej interesująca. Wykonuje większość pracy przy tworzeniu znacznika czasu, polegającej na konwersji daty i czasu na pewną liczbę sekund od początku Epoki. Wywołujący przekazuje tablicę (dość wymyślnie nazwaną a) zawierającą sześć wartości: rok łącznie ze stuleciem, miesiąc jako liczbę między 1 a 12, dzień miesiąca, godzinę jako liczbę między 0 a 23, minuty i sekundy.

Do wstępnego obliczenia liczby sekund w godzinie, sekund w dniu i sekund w roku funkcja wykorzystuje kilka zmiennych lokalnych. Często kod w C po prostu zapisuje dane wyrażenie in-line, spodziewając się, że kompilator wykona złożenie stałych (constant folding). Np., większość kompilatorów C przekształciłaby `60 * 60' na `3600' w czasie kompilacji, zamiast przeliczać to każdorazowo w czasie wykonania. Wstępne obliczenie tych wartości powoduje, że funkcja jest bardziej efektywna.

# zamienia datę na sekundy
function _tm_addup(a,    razem, roksek, dziensek,
                         godzsek, i, j)
{
    godzsek = 60 * 60
    dziensek = 24 * godzsek
    roksek = 365 * dziensek

    razem = (a[1] - 1970) * roksek

    # dodatkowy dzień dla lat przestępnych
    for (i = 1970; i < a[1]; i++)
        if (_tm_isleap(i))
            razem += dziensek

    j = _tm_isleap(a[1])
    for (i = 1; i < a[2]; i++)
        razem += _tm_mies[j, i] * dziensek

    razem += (a[3] - 1) * dziensek
    razem += a[4] * godzsek
    razem += a[5] * 60
    razem += a[6]

    return razem
}

Funkcja zaczyna od pierwszego przybliżenia wszystkich sekund pomiędzy północą 1 stycznia 1970,(23) a początkiem bieżącego roku. Następnie przechodzi przez wszystkie te lata i dla każdego roku przestępnego dodaje równowartość w sekundach jednego dnia.

Zmienna j przechowuje jeden lub zero, w zależności od tego czy bieżący rok jest czy nie jest przestępny. Dla każdego miesiąca bieżącego roku poprzedzającego aktualny miesiąc dodaje liczbę sekund tego miesiąca, wykorzystując odpowiednią pozycję tablicy _tm_mies.

Na koniec, dodaje sekundy odpowiadające liczbie dni poprzedzających bieżący oraz liczbie godzin, minut i sekund bieżącego dnia.

Wynikiem jest ilość sekund od 1 stycznia 1970. Wartość ta nie jest jednak jeszcze tym, czego potrzebujemy. Opiszemy pokrótce powód.

Główna funkcja mktime pobiera pojedynczy argument łańcuchowy. Łańcuch przedstawia datę i czas w postaci "kanonicznej" (ustalonej). Powinno to być "rok mies dzien godz min sek".

# mktime --- zamień datę na sekundy,
#            koryguj z uwagi na strefę czasową

function mktime(str,    wyn1, wyn2, a, b, i, j, t, rozn)
{
    i = split(str, a, " ")    # nie polegaj na FS

    if (i != 6)
        return -1

    # wymuś numeryczne
    for (j in a)
        a[j] += 0

    # sprawdź poprawność
    if (a[1] < 1970 ||
        a[2] < 1 || a[2] > 12 ||
        a[3] < 1 || a[3] > 31 ||
        a[4] < 0 || a[4] > 23 ||
        a[5] < 0 || a[5] > 59 ||
        a[6] < 0 || a[6] > 60 )
            return -1

    wyn1 = _tm_addup(a)
    t = strftime("%Y %m %d %H %M %S", wyn1)

    if (_tm_debug)
        printf("(%s) -> (%s)\n", str, t) > "/dev/stderr"

    split(t, b, " ")
    wyn2 = _tm_addup(b)

    rozn = wyn1 - wyn2

    if (_tm_debug)
        printf("rozn = %d sekund\n", rozn) > "/dev/stderr"

    wyn1 += rozn

    return wyn1
}

Funkcja najpierw dzieli łańcuch, stosując jako separatory spacje i tabulacje i umieszczając wynik w tablicy. Jeżeli w tablicy nie ma sześciu elementów, to zwraca błąd, sygnalizowany jako wartość -1. Następnie wymusza numeryczność każdego elementu tablicy, dodając doń zero. Kolejna instrukcja, `if', upewnia się, że każdy element mieści się w dopuszczalnym zakresie. (Ta kontrola powinna zostać później poszerzona, np., by upewnić się, że dzień miesiąca jest w poprawnym zakresie dla konkretnego, podanego miesiąca.) Wszystko to w gruncie rzeczy stanowi wstępną konfigurację i kontrolę błędów.

Przypomnijmy, że _tm_addup tworzyła wartość w sekundach od północy 1 stycznia 1970. Wartość ta nie nadaje się wprost do użytku jako wynik, którego potrzebujemy, gdyż obliczenia nie biorą pod uwagę lokalnej strefy czasowej. Inaczej mówiąc, wartość ta przedstawia ilość sekund od początku Epoki, ale tylko dla UTC (czasu uniwersalnego, Greenwich). Jeśli lokalna strefa czasowa leży na wschód lub zachód od UTC, to do wynikowego znacznika czasu należy dodać, lub odjąć od niego, pewną liczbę godzin.

Na przykład, 18:23 w Atlancie, stan Georgia (USA), jest normalnie pięć godzin na zachód (za) UTC. Przy czasie letnim jest to tylko cztery godziny za UTC. Jeśli wywołujemy mktime w Atlancie, z argumentem "1993 5 23 18 23 12", wynikiem z _tm_addup będzie 18:23 UTC, co stanowi dopiero 14:23 w Atlancie. Niezbędne jest dodanie do otrzymanego wyniku kolejnych sekund o równowartości czterech godzin.

Jak mktime może stwierdzić, jak daleko do UTC się znajduje? To jest zaskakująco łatwe. Zwrócony znacznik czasu przedstawia czas przekazany do mktime jako UTC. Znacznikiem tym można z powrotem nakarmić strftime, które sformatuje go jako czas lokalny, tj. tak, jakby miał już w sobie dodaną różnicę w stosunku do UTC. Zrobiono to przez przekazanie do strftime łańcucha "%Y %m %d %H %M %S" jako argumentu formatu. Zwróci ono obliczony znacznik w formacie pierwotnego łańcucha. Wynik reprezentuje czas uwzględniający różnicę do UTC. Gdy nowy czas jest konwertowany z powrotem na znacznik czasu, różnica pomiędzy tymi dwoma znacznikami jest różnicą (w sekundach) między lokalną strefą czasową a UTC. Różnica ta jest następnie dodawana do pierwotnego wyniku. Przykład pokazujący to przedstawiono poniżej.

Na koniec, mamy "główny" program do przetestowania funkcji.

BEGIN  {
    if (_tm_test) {
        printf "Wprowadź datę yyyy mm dd hh mm ss: "
        getline _tm_test_date
        t = mktime(_tm_test_date)
        r = strftime("%Y %m %d %H %M %S", t)
        printf "Otrzymujemy zwrotnie (%s)\n", r
    }
}

Cały program do sterowania diagnostyką wyjścia i włączenia testu w końcowej regule BEGIN korzysta z dwu zmiennych, którym wartość można nadać w wierszu poleceń. Oto wynik przebiegu testu. (Zauważ, że wyjściem diagnostycznym jest standardowe wyjście błędów, zaś wyjściem testu standardowe wyjście.)

$ gawk -f mktime.awk -v _tm_test=1 -v _tm_debug=1
-| Wprowadź datę yyyy mm dd hh mm ss: 1993 5 23 15 35 10
error--> (1993 5 23 15 35 10) -> (1993 05 23 11 35 10)
error--> rozn = 14400 sekund
-| Otrzymujemy zwrotnie (1993 05 23 15 35 10)

Wprowadzono czas 15:25, 23 maja 1993. Pierwszy wiersz wyjścia diagnostycznego pokazuje czas wynikowy jako UTC -- cztery godziny wyprzedzający czas lokalny. Drugi wiersz pokazuje, że różnica wynosi 14400 sekund, co stanowi cztery godziny. (Różnica jest tylko czterogodzinna, gdyż w maju obowiązuje tu czas letni.) Ostatni wiersz wyjścia testowego pokazuje, że algorytm wyrównywania strefy czasowej działa: zwrócony czas jest taki sam jak czas wprowadzony.

Program ten nie rozwiązuje ogólnego problemu zamiany dowolnej daty na znacznik czasu. Problem taki jest bardzo zagmatwany. Jednak funkcja mktime daje fundament, na którym można budować rozwiązanie. Inny program może przekształcać nazwy miesięcy na ich numery kolejne i czasy AM/PM [tłum:przed/po południu, stosowane przy zegarze 12-godzinnym] na czas 24-godzinny, by utworzyć format "kanoniczny", jakiego wymaga mktime.

15.8. Obsługa daty i czasu

Funkcje systime i strftime opisane w 12.5. Funkcje obsługi znaczników czasu, zapewniają minimalną funkcjonalność niezbędną przy działaniach na czasie w postaci czytelnej dla człowieka. Mimo, że strftime jest rozległa, formaty sterujące niekonieczne są łatwe do zapamiętania i intuicyjnie oczywiste przy czytaniu programu.

Poniższa funkcja, gettimeofday, zapełnia dostarczoną przez użytkownika tablicę sformatowaną informacją o czasie. Zwraca łańcuch z bieżącym czasem sformatowanym tak samo, jak przez narzędzie date.

# gettimeofday --- pobierz datę i czas w dającym się użyć formacie
# Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 , Public Domain, May 1993
#
# Zwraca łańcuch w formacie wyjściowym date(1)
# Wypełnia tablicę argumentu czasu poszczególnymi wartościami:
#    czas["sekunda"]         -- sekundy (0 - 59)
#    czas["minuta"]          -- minuty (0 - 59)
#    czas["godzina"]         -- godziny (0 - 23)
#    czas["altgodzina"]      -- godziny (0 - 12)
#    czas["dzienmca"]        -- dzień miesiąca (1 - 31)
#    czas["miesiac"]         -- miesiąc roku (1 - 12)
#    czas["nazwamca"]        -- nazwa miesiąca
#    czas["mckrotko"]        -- krótka nazwa miesiąca
#    czas["rok"]             -- rok w stuleciu (0 - 99)
#    czas["pelnyrok"]        -- rok ze stuleciem (19xx lub 20xx)
#    czas["dzientyg"]        -- dzien tygodnia (niedziela = 0)
#    czas["altdzientyg"]     -- dzien tygodnia (poniedziałek = 0)
#    czas["numtyg"]          -- numer tygodnia, niedziela pierwsza
#    czas["altnumtyg"]       -- numer tygodnia, poniedziałek pierwszy
#    czas["nazwadnia"]       -- nazwa dnia tygodnia
#    czas["dzienkrotko"]     -- krótka nazwa dnia tygodnia
#    czas["dzienroku"]       -- dzień roku (0 - 365)
#    czas["strefa"]          -- skrót nazwy strefy czasowej
#    czas["ampm"]            -- określenie AM lub PM

function gettimeofday(czas,    ret, teraz, i)
{
    # pobierz czas raz, unikając zbędnych wywołań systemowych
    teraz = systime()

    # zwróć wyjście w stylu date(1)
    ret = strftime("%a %b %d %H:%M:%S %Z %Y", teraz)

    # wyczyść tablicę docelową
    for (i in czas)
        delete czas[i]

    # wypełnij wartościami, wymuszając na numerycznych
    # wartościach ich numeryczność przez dodanie 0
    czas["sekunda"]     = strftime("%S", now) + 0
    czas["minuta"]      = strftime("%M", now) + 0
    czas["godzina"]     = strftime("%H", now) + 0
    czas["altgodzina"]  = strftime("%I", now) + 0
    czas["dzienmca"]    = strftime("%d", now) + 0
    czas["miesiac"]     = strftime("%m", now) + 0
    czas["nazwamca"]    = strftime("%B", now)
    czas["mckrotko"]    = strftime("%b", now)
    czas["rok"]         = strftime("%y", now) + 0
    czas["pelnyrok"]    = strftime("%Y", now) + 0
    czas["dzientyg"]    = strftime("%w", now) + 0
    czas["altdzientyg"] = strftime("%u", now) + 0
    czas["nazwadzien"]  = strftime("%A", now)
    czas["dzienkrotko"] = strftime("%a", now)
    czas["dzienroku"]   = strftime("%j", now) + 0
    czas["strefa"]      = strftime("%Z", now)
    czas["ampm"]        = strftime("%p", now)
    czas["numtyg"]      = strftime("%U", now) + 0
    czas["altnumtyg"]   = strftime("%W", now) + 0

    return ret
}

Indeksy łańcuchowe są łatwiejsze w korzystaniu i czytaniu niż rozmaite formaty wymagane przez strftime. Funkcja ta jest wykorzystywana przez program alarm przedstawiony w 16.2.2. Program-budzik.

Funkcję gettimeofday przedstawiono powyżej tak, jak została napisana. Ogólniejsza konstrukcja funkcji pozwoliłaby użytkownikowi podanie opcjonalnej wartości znacznika czasu, która byłaby użyta zamiast bieżącego czasu.

15.9. Obsługa przejść między plikami

Każda z reguł BEGIN i END wykonywana jest dokładnie raz, odpowiednio na początku i na końcu programu awk (zob. 8.1.5. Wzorce specjalne BEGIN i END). Sami, jako autorzy gawk, mieliśmy kiedyś użytkownika, który błędnie sądził, że reguła BEGIN jest wykonywana na początku każdego pliku a END na końcu każdego pliku danych. Gdy został poinformowany, że tak nie jest, poprosił byśmy dodali specjalne wzorce do gawk, o nazwach BEGIN_FILE i END_FILE, które miałyby pożądane zachowanie. Nawet dostarczył nam kod, by to zrobić.

Jednak, po krótkim namyśle, doszedłem do następującego programu bibliotecznego. Organizuje on wywoływanie dwu zapewnianych przez użytkownika funkcji, beginfile i endfile, na początku i końcu każdego pliku danych. Program, oprócz tego, że rozwiązuje problem w jedynie dziewięciu(!) linijkach kodu, robi to w sposób przenośny. Konstrukcja ta będzie działać w dowolnej implementacji awk.

# transfile.awk
#
# Daje użytkownikowi obsługę przejścia między plikami
#
# Użytkownik musi zapewnić funkcje beginfile() i endfile(),
# z których każda pobiera odpowiednio nazwę pliku
# rozpoczynanego lub kończonego.
#
# Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 , January 1992
# Public Domain

FILENAME != _staryplik \
{
    if (_staryplik != "")
        endfile(_staryplik)
    _staryplik = FILENAME
    beginfile(FILENAME)
}

END   { endfile(FILENAME) }

Plik ten musi być wczytany przed "głównym" programem użytkownika, tak by reguła, jaką zawiera, była wykonywana jako pierwsza.

Reguła ta opiera się na zmiennej awk FILENAME, która zmienia się automatycznie dla każdego nowego pliku danych. Bieżąca nazwa pliku przechowywana jest w zmiennej prywatnej, _staryplik. Jeżeli FILENAME nie jest równe _staryplik, to rozpoczęto przetwarzanie nowego pliku danych, i konieczne jest wywołanie endfile dla starego pliku. Ponieważ endfile powinna być wywołana jeśli przetworzono plik, program upewnia się najpierw, czy _staryplik nie jest łańcuchem pustym. Następnie przypisuje nazwę bieżącego pliku zmiennej _staryplik i wywołuje beginfile dla tego pliku. Ponieważ, jak wszystkie zmienne awk, _staryplik będzie inicjowane łańcuchem pustym, reguła ta wykona się poprawnie nawet dla pierwszego pliku danych.

Program zawiera także regułę END, do wykonania końcowego przetwarzania ostatniego pliku. Ponieważ END przychodzi przed wszystkimi innymi ewentualnymi regułami END podanymi w programie "głównym", najpierw zostanie wywołana endfile. Raz jeszcze zalety wielokrotnych reguł BEGIN i END powinny być bezsporne.

Nasza wersja ma ten sam kłopot, co pierwsza wersja nextfile (zob. 15.2. Implementacja nextfile jako funkcji). Jeżeli ten sam plik danych pojawia się w wierszu poleceń dwa razy z rzędu, to endfile i beginfile nie zostaną wykonane na końcu pierwszego przebiegu i na początku drugiego. Ta wersja rozwiązuje problem.

# ftrans.awk --- obsługa  przejścia między plikami
#
# użytkownik zapewnia funkcje beginfile() i endfile()
#
# Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 , November 1992
# Public Domain

FNR == 1 {
    if (_plik_ != "")
        endfile(_plik_)
    _plik_ = FILENAME
    beginfile(FILENAME)
}

END  { endfile(_plik_) }

W 16.1.7. Zliczanie rzeczy, zobaczymy, jak można wykorzystać tę funkcję biblioteczną, i jak upraszcza ona pisanie głównego programu.

15.10. Przetwarzanie opcji wiersza poleceń

Większość narzędzi w systemach zgodnych z POSIX przyjmuje w wierszu poleceń opcje lub "przełączniki", które służą do zmiany sposobu zachowania się programu. awk jest przykładem takiego programu (zob. 14.1. Opcje wiersza poleceń). Często opcje pobierają argumenty, dane, których program potrzebuje do poprawnego wykonania danej opcji wiersza poleceń. Na przykład, opcja `-F' narzędzia awk wymaga łańcucha, jaki ma być użyty jako separator pól. Pierwsze wystąpienie w wierszu poleceń `--' albo łańcucha nie rozpoczynającego się od `-' kończy opcje.

Większość systemów uniksowych do przetwarzania argumentów wiersza poleceń udostępnia funkcję C o nazwie getopt. Programista dostarcza łańcuch opisujący jednoliterowe opcje. Jeżeli opcja wymaga argumentu, w łańcuchu występuje po nim dwukropek. Do getopt są też przekazywane ilość i wartości argumentów wiersza poleceń. Wywoływana jest ona w pętli. getopt przetwarza argumenty wiersza poleceń z uwagi na litery opcji. Przy każdym obrocie pętli zwraca pojedynczy znak reprezentujący kolejną literę opcji, jaką znalazła, albo `?' jeśli napotkała niepoprawną opcję. Gdy zwraca -1, to w wierszu poleceń ma już nie opcji.

Przy korzystaniu z getopt opcje nie pobierające argumentów można grupować, łącząc je ze sobą. Ponadto, opcje pobierające argumenty wymagają obecności argumentu. Argument może występować bezpośrednio po literze opcji albo być osobnym argumentem wiersza poleceń.

Zakładając, że mamy hipotetyczny program, który przyjmuje trzy opcje wiersza poleceń `-a', `-b' i `-c', a `-b' wymaga argumentu, poprawne są wszystkie poniższe sposoby wywołania programu:

prog -a -b foo -c dane1 dane2 dane3
prog -ac -bfoo -- dane1 dane2 dane3
prog -acbfoo dane1 dane2 dane3

Zwróć uwagę, że gdy argument jest połączony z opcją, reszta argumentów wiersza poleceń uważana jest za argument tej opcji. W powyższym przykładzie, `-acbfoo' wskazuje, że podano wszystkie z opcji `-a', `-b' i `-c', a `foo' jest argumentem opcji `-b'.

getopt dostarcza cztery zewnętrzne zmienne do wykorzystania przez programistę.

optind
Indeks w tablicy wartości argumentów (argv), gdzie znaleziono pierwszy nie będący opcją argument wiersza poleceń.
optarg
Wartość łańcuchowa argumentu aktualnej opcji.
opterr
Zwykle getopt wypisuje komunikat o błędzie gdy znajdzie niepoprawną opcję. Nadanie opterr wartości zero wyłącza tę cechę. (Program użytkowy może zechcieć wypisać własny komunikat o błędzie.)
optopt
Litera reprezentująca aktualną opcję wiersza poleceń. Mimo, że zwykle nie jest udokumentowana, większość wersji zapewnia tę zmienną.

Poniższy fragment w C pokazuje, jak getopt mógłby przetwarzać argumenty wiersza poleceń przekazane do awk.

int
main(int argc, char *argv[])
{
    ...
    /* wypisz nasz własny komunikat */
    opterr = 0;
    while ((c = getopt(argc, argv, "v:f:F:W:")) != -1) {
        switch (c) {
        case 'f':    /* plik */
            ...
            break;
        case 'F':    /* separator pól */
            ...
            break;
        case 'v':    /* przypisanie zmiennej */
            ...
            break;
        case 'W':    /* rozszerzenie */
            ...
            break;
        case '?':
        default:
            usage();
            break;
        }
    }
    ...
}

Nawiasem mówiąc, gawk w rzeczywistości zarówno do przetwarzania zwykłych, jak i długich opcji w stylu GNU, korzysta z funkcji GNU getopt_long (zob. 14.1. Opcje wiersza poleceń).

Abstrakcja zapewniana przez getopt jest bardzo przydatna i w programach awk również byłaby bardzo poręczna. Oto wersja getopt napisana w awk. Ta funkcja ujawnia jedną z największych słabości awk: jest kiepski w operowaniu na pojedynczych znakach. Dla uzyskania dostępu do indywidualnych znaków niezbędne jest powtarzanie wywołań substr (zob. 12.3. Funkcje wbudowane działające na łańcuchach).

Kod jest omawiany po kawałku.

# getopt --- robi w awk funkcję getopt(3) z biblioteki C
#
# 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 
# Public domain
#
# Initial version: March, 1991
# Revised: May, 1993

# Zmienne zewnętrzne:
#    Optind -- indeks w ARGV pierwszego argumentu nie-opcyjnego
#    Optarg -- wartość łańcuchowa argumentu bieżącej opcji
#    Opterr -- jeśli niezerowe, wypisuje naszą diagnostykę
#    Optopt -- litera bieżącej opcji

# Zwraca
#    -1     na końcu opcji
#    ?      dla nierozpoznanej opcji
#    <c>    znak reprezentujący bieżącą opcję

# Dane prywatne
#    _opti  indeks w opcji wieloflagowej, np. -abc

Funkcja zaczyna się od dokumentacji: kto napisał kod, i kiedy został on wypuszczony, po czym następuje lista wykorzystywanych zmiennych globalnych, jakie są zwracane wartości i co znaczą, i wszelkie zmienne globalne, które dla tej funkcji bibliotecznej są "prywatne". Taka dokumentacja jest kluczowa dla każdego programu, a szczególnie dla funkcji bibliotecznych.

function getopt(argc, argv, options,    optl, thisopt, i)
{
    optl = length(options)
    if (optl == 0)        # nie podano opcji
        return -1

    if (argv[Optind] == "--") {  # wszystko zrobione
        Optind++
        _opti = 0
        return -1
    } else if (argv[Optind] !~ /^-[^: \t\n\f\r\v\b]/) {
        _opti = 0
        return -1
    }

Funkcja sprawdza najpierw, czy rzeczywiście została wywołana z łańcuchem opcji (parametr options). Jeśli options ma długość zerową, getopt od razu zwraca -1.

Następną rzeczą do sprawdzenia jest koniec opcji. Ciąg `--' kończy opcje wiersza poleceń, podobnie każdy argument wiersza poleceń nie rozpoczynający się od `-'. Do przechodzenia przez tablicę argumentów wiersza poleceń używana jest Optind. Zachowuje ona swoją wartość pomiędzy wywołaniami getopt, gdyż jest zmienną globalną.

Użyte wyrażenie regularne, /^-[^: \t\n\f\r\v\b]/, jest być może nieco za ostre. Kontroluje, czy po `-' występuje coś, co nie jest białym znakiem ani dwukropkiem. Jeśli bieżący argument wiersza poleceń nie pasuje do tego wzorca, to nie jest opcją i kończy przetwarzanie opcji.

    if (_opti == 0)
        _opti = 2
    thisopt = substr(argv[Optind], _opti, 1)
    Optopt = thisopt
    i = index(options, thisopt)
    if (i == 0) {
        if (Opterr)
            printf("%c -- invalid option\n",
                                  thisopt) > "/dev/stderr"
        if (_opti >= length(argv[Optind])) {
            Optind++
            _opti = 0
        } else
            _opti++
        return "?"
    }

Zmienna _opti zapamiętuje pozycję w bieżącym argumencie wiersza poleceń (argv[Optind]). W przypadku, gdy połączono wiele opcji z jednym `-' (np. `-abx'), należy zwracać je użytkownikowi po jednej.

Jeśli _opti jest równe zero, otrzymuje wartość dwa, indeks następnego znaku, jaki należy zbadać w łańcuchu (pomijamy `-', stojące na pozycji numer jeden). Znak ten, uzyskany z substr, przechowuje zmienna thisopt. Jest on równocześnie przechowywany w Optopt, do wykorzystania przez program główny.

Jeśli thisopt nie ma w łańcuchu options, to jest to niepoprawna opcja. Jeżeli Opterr jest niezerowe, getopt wypisze na standardowym wyjściu błędów komunikat o błędzie podobny do komunikatu getopt w wersji C.

Ponieważ opcja jest niepoprawna, należy ją pominąć i przejść do następnego znaku opcyjnego. Jeżeli _opti jest większe lub równe długości bieżącego argumentu wiersza poleceń, to należy przejść do kolejnego argumentu. Wówczas zwiększane jest Optind i zerowane _opti. W przeciwnym przypadku, Optind jest pozostawiane bez zmian, a zwiększane jest tylko _opti.

W każdym z przypadków, ponieważ opcja jest niepoprawna, getopt zwraca `?'. Program główny może zbadać Optopt jeśli potrzebuje wiedzieć, jaka była faktycznie litera niepoprawnej opcji.

    if (substr(options, i + 1, 1) == ":") {
        # pobierz argument opcji
        if (length(substr(argv[Optind], _opti + 1)) > 0)
            Optarg = substr(argv[Optind], _opti + 1)
        else
            Optarg = argv[++Optind]
        _opti = 0
    } else
        Optarg = ""

Jeżeli dana opcja wymaga argumentu, to w łańcuchu options po jej literze występuje dwukropek. Jeżeli w bieżącym argumencie wiersza poleceń (argv[Optind]) pozostały jakieś znaki, to reszta tego łańcucha przypisywana jest do Optarg. W przeciwnym razie używany jest następny argument wiersza poleceń (`-xFOO' versus `-x FOO'). W obu przypadkach zerowane jest _opti, gdyż w bieżącym argumencie wiersza poleceń nie ma już znaków pozostałych do zbadania.

    if (_opti == 0 || _opti >= length(argv[Optind])) {
        Optind++
        _opti = 0
    } else
        _opti++
    return thisopt
}

Na koniec, jeżeli _opti jest albo zerowe albo większe niż długość bieżącego argumentu wiersza poleceń, to znaczy to, że ten element w argv jest już w całości przetworzony. Zwiększane jest więc Optind, tak by wskazywało na kolejny element argv. Jeżeli żaden z tych warunków nie jest prawdziwy, to zwiększane jest tylko _opti, tak by przy następnym wywołaniu getopt była przetwarzana kolejna litera opcji.

BEGIN {
    Opterr = 1    # domyślnie włączona diagnostyka
    Optind = 1    # pomiń ARGV[0]

    # program testowy
    if (_getopt_test) {
        while ((_go_c = getopt(ARGC, ARGV, "ab:cd")) != -1)
            printf("c = <%c>, optarg = <%s>\n",
                                       _go_c, Optarg)
        printf("argumenty nieopcyjne:\n")
        for (; Optind < ARGC; Optind++)
            printf("\tARGV[%d] = <%s>\n",
                                    Optind, ARGV[Optind])
    }
}

Reguła BEGIN inicjuje zarówno Opterr, jak i Optind, jedynką. Opterr otrzymuje wartość jeden, gdyż domyślnym zachowaniem getopt jest wypisywanie komunikatu diagnostycznego po zauważeniu niepoprawnej opcji. Optind otrzymuje wartość jeden, gdyż nie ma powodu, by badać nazwę programu, znajdującą się w ARGV[0].

Dalsza część reguły BEGIN jest prostym programem testowym. Oto wynik dwu przykładowych przebiegów programu testowego.

$ awk -f getopt.awk -v _getopt_test=1 -- -a -cbARG bax -x
-| c = <a>, optarg = <>
-| c = <c>, optarg = <>

-| c = <b>, optarg = <ARG>
-| argumenty nieopcyjne:
-|         ARGV[3] = <bax>
-|         ARGV[4] = <-x>

$ awk -f getopt.awk -v _getopt_test=1 -- -a -x -- xyz abc
-| c = <a>, optarg = <>
error--> x -- invalid option
-| c = <?>, optarg = <>

-| argumenty nieopcyjne:
-|         ARGV[4] = <xyz>
-|         ARGV[5] = <abc>

Pierwsze `--' kończy argumenty przekazywane do awk, więc awk nie próbuje interpretować `-a' itd. jako własnych opcji.

Do przetwarzania swoich opcji korzysta z getopt kilka programów przykładowych przedstawionych w 16. Praktyczne programy awk.

15.11. Czytanie bazy użytkowników

Plik specjalny `/dev/user' (zob. 6.7. Specjalne nazwy plików w gawk) daje dostęp do związanych z bieżącym użytkownikiem numerycznych rzeczywistych i efektywnych identyfikatorów użytkownika i grupy, oraz, jeśli jest dostępny, zestawu dodatkowych grup użytkownika. Jednak, ponieważ są to liczby, przeciętnemu użytkownikowi nie dają zbyt użytecznej informacji. Musi istnieć jakaś metoda znalezienia danych o użytkowniku związanych z numerami użytkowników i grup. Niniejsza sekcja przedstawia pakiet funkcji do pobierania informacji z bazy danych użytkowników. Zob. 15.12. Czytanie bazy grup, gdzie jest podobny pakiet, pobierający informacje z bazy grup.

Standard POSIX nie definiuje pliku, w którym trzymane są dane o użytkownikach. Zamiast tego, zapewnia plik nagłówkowy <pwd.h> i kilka podprogramów w języku C służących do uzyskiwania informacji o użytkowniku. Najważniejszą z nich jest getpwent, od "get password entry" (pobierz wpis z haseł). Użyte "password" pochodzi od pierwotnej bazy danych o użytkownikach, `/etc/passwd', która przechowuje dane o użytkowniku, razem z zakodowanymi hasłami (stąd nazwa).

Mimo, że program awk mógłby po prostu bezpośrednio czytać `/etc/passwd' (format jest dobrze znany), z powodu sposobu, w jaki pliki haseł obsługiwane są w systemach sieciowych, plik ten może nie zawierać pełnej informacji o zbiorze użytkowników systemu.

Aby być pewnym możności stworzenia czytelnej, pełnej wersji bazy użytkowników, trzeba napisać mały program w C, który wywołuje getpwent. Funkcję getpwent zdefiniowano tak, by zwracała wskaźnik do struct passwd. Przy każdym wywołaniu zwraca kolejną pozycję bazy danych. Jeżeli nie ma już więcej pozycji, zwraca NULL, wskaźnik pusty. Gdy się to stanie, program C powinien wywołać endpwent, by zamknąć bazę. Oto pwcat, program w C, który wypisuje, jak cat, bazę haseł.

/*
 * pwcat.c
 *
 * Tworzy drukowalną wersją bazy haseł
 *
 * Arnold Robbins
 * 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 
 * May 1993
 * Public Domain
 */

#include <stdio.h>
#include <pwd.h>

int
main(argc, argv)
int argc;
char **argv;
{
    struct passwd *p;

    while ((p = getpwent()) != NULL)
        printf("%s:%s:%d:%d:%s:%s:%s\n",
            p->pw_name, p->pw_passwd, p->pw_uid,
            p->pw_gid, p->pw_gecos, p->pw_dir, p->pw_shell);

    endpwent();
    exit(0);
}

Nie przejmuj się, jeżeli nie rozumiesz C. Wyjściem z pwcat jest baza użytkowników, w tradycyjnym formacie `/etc/passwd', o polach rozdzielanych dwukropkami. Polami są:

Login name
Nazwa zgłoszeniowa użytkownika.
Encrypted password
Zakodowane hasło użytkownika. Na niektórych systemach może nie być dostępne.
User-ID
Numeryczny identyfikator danego użytkownika.
Group-ID
Numeryczny identyfikator grupy użytkownika.
Full name
Pełna nazwa (imię, nazwisko) użytkownika, i być może inne dane z nim związane.
Home directory
Katalog zgłoszeniowy, "domowy" użytkownika (znany programującym w powłoce jako $HOME).
Login shell
Program, który zostanie uruchomiony gdy użytkownik się zaloguje. Jest zwykle to powłoka, taka jak Bash (Gnu Bourne-Again shell).

Oto kilka typowych dla wyjścia z pwcat wierszy.

$ pwcat
-| root:3Ov02d5VaUPB6:0:1:Operator:/:/bin/sh
-| nobody:*:65534:65534::/:
-| daemon:*:1:1::/:
-| sys:*:2:2::/:/bin/csh
-| bin:*:3:3::/bin:
-| arnold:xyzzy:2076:10:Arnold Robbins:/home/arnold:/bin/sh
-| miriam:yxaay:112:10:Miriam Robbins:/home/miriam:/bin/sh
-| andy:abcca2:113:10:Andy Jacobs:/home/andy:/bin/sh
...

Po tym wprowadzeniu pokażemy grupę funkcji do pozyskiwania danych o użytkowniku. Mamy tu kilka funkcji, odpowiadających funkcjom C o tych samych nazwach.

# passwd.awk --- dostęp do danych pliku haseł
# Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 , Public Domain
# May 1993

BEGIN {
    # dopasuj to do własnego systemu
    _pw_awklib = "/usr/local/libexec/awk/"
}

function _pw_init(    oldfs, oldrs, olddol0, pwcat)
{
    if (_pw_inited)
        return
    oldfs = FS
    oldrs = RS
    olddol0 = $0
    FS = ":"
    RS = "\n"
    pwcat = _pw_awklib "pwcat"
    while ((pwcat | getline) > 0) {
        _pw_byname[$1] = $0
        _pw_byuid[$3] = $0
        _pw_bycount[++_pw_total] = $0
    }
    close(pwcat)
    _pw_count = 0
    _pw_inited = 1
    FS = oldfs
    RS = oldrs
    $0 = olddol0
}

Reguła BEGIN przypisuje zmiennej prywatnej katalog, w którym przechowywany jest pwcat. Ponieważ pwcat wykorzystujemy jako pomoc dla procedury bibliotecznej awk, zdecydowaliśmy się umieścić go w `/usr/local/libexec/awk'. W swoim systemie możesz trzymać go w jakimś innym katalogu.

Funkcja _pw_init przechowuje trzy kopie danych o użytkowniku w trzech tablicach asocjacyjnych. Tablice są indeksowane nazwą użytkownika (_pw_byname), numerycznym identyfikatorem użytkownika (_pw_byuid) i kolejnością występowania (_pw_bycount).

Zmienną _pw_inited posłużono się w celu poprawy efektywności: _pw_init wystarczy wywołać tylko raz.

Ponieważ nasza funkcja do czytania danych z pwcat wykorzystuje instrukcję getline, najpierw zapamiętuje wartości FS, RS i $0. Jest to konieczne, gdyż omawiane funkcje mogą zostać wywołane z dowolnego miejsca programu użytkownika, a może on mieć swoje własne wartości FS i RS.

Główna część funkcji wykorzystuje pętlę do czytania wierszy bazy, podziału danego wiersza na pola, a następnie, jak tego potrzebujemy, zapamiętania go w każdej z tablic. Po wykonaniu pętli, _pw_init robi porządki, zamykając potok, przypisując _pw_inited jedynkę i odtwarzając FS, RS i $0. Posługiwanie się _pw_count będzie objaśnione niżej.

function getpwnam(name)
{
    _pw_init()
    if (name in _pw_byname)
        return _pw_byname[name]
    return ""
}

Funkcja getpwnam pobiera nazwę użytkownika jako argument łańcuchowy. Jeśli dany użytkownik jest w bazie, zwraca odpowiedni wiersz. W przeciwnym razie zwraca łańcuch pusty.

function getpwuid(uid)
{
    _pw_init()
    if (uid in _pw_byuid)
        return _pw_byuid[uid]
    return ""
}

Podobnie, funkcja getpwuid pobiera jako argument numeryczny identyfikator użytkownika. Jeśli taki numer użytkownika jest w bazie, zwraca odpowiedni wiersz. W przeciwnym razie zwraca łańcuch pusty.

function getpwent()
{
    _pw_init()
    if (_pw_count < _pw_total)
        return _pw_bycount[++_pw_count]
    return ""
}

Funkcja getpwent po prostu przechodzi przez bazę, po jednej pozycji naraz. Korzysta z _pw_count do zapamiętywania swojej bieżącej pozycji w tablicy _pw_bycount.

function endpwent()
{
    _pw_count = 0
}

Funkcja endpwent zeruje _pw_count, tak że kolejne wywołania getpwent rozpoczną się znów od początku.

Świadomą decyzją projektową w pakiecie jest to, że każdy z podprogramów wywołuje _pw_init w celu zainicjowania tablic bazy. Dodatkowy koszt uruchomienia osobnego procesu do stworzenia bazy użytkowników i operacji wejścia/wyjścia do jej przeszukania poniesiony zostanie tylko jeśli program główny użytkownika faktycznie wywoła jedną z tych funkcji. Jeżeli razem z programem użytkownika wczytywany jest plik biblioteczny, ale żaden z podprogramów nie zostanie nigdy wywołany, to nie będzie żadnego dodatkowego narzutu na czas wykonania. (Alternatywą byłoby przesunięcie ciała _pw_init do reguły BEGIN, która zawsze uruchamiałaby pwcat. Upraszcza to kod, lecz uruchamia dodatkowy proces, który może nigdy nie być potrzebny.)

Z kolei, wywołanie _pw_init nie jest zbyt kosztowne, gdyż zmienna _pw_inited powstrzymuje program od czytania danych więcej niż raz. Jeśli chcielibyśmy ze swojego programu wycisnąć ostatni cykl, to kontrola _pw_inited powinna być przesunięta poza _pw_init i powielona w każdej z pozostałych funkcji. W praktyce nie jest to konieczne, gdyż większość programów awk jest ograniczona wejściem/wyjściem, a tylko zapchałoby to kod.

Funkcji tych używa program id w 16.1.3. Wypisywanie informacji o użytkowniku.

15.12. Czytanie bazy grup

Większość rozważań przedstawionych w 15.11. Czytanie bazy użytkowników, odnosi się również do bazy grup. Mimo, że tradycyjnie istnieje dobrze znany plik, `/etc/group', w dobrze znanym formacie, standard POSIX zapewnia wyłącznie zestaw podprogramów bibliotecznych C (<grp.h> i getgrent) dających dostęp do tych danych. Mimo tego nawet, że plik ten istnieje, możliwe jest, że nie zawiera pełnej informacji. Z tego powodu, tak jak w przypadku bazy użytkowników, niezbędne jest posiadanie małego programu w C, który tworzy jako swoje wyjście bazę grup.

Oto grcat, program w C, który wypisuje, jak cat, bazę grup.

/*
 * grcat.c
 *
 * tworzy drukowalną wersję bazy grup
 *
 * Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 
 * May 1993
 * Public Domain
 */

#include <stdio.h>
#include <grp.h>

int
main(argc, argv)
int argc;
char **argv;
{
    struct group *g;
    int i;

    while ((g = getgrent()) != NULL) {
        printf("%s:%s:%d:", g->gr_name, g->gr_passwd,
                                            g->gr_gid);
        for (i = 0; g->gr_mem[i] != NULL; i++) {
            printf("%s", g->gr_mem[i]);
            if (g->gr_mem[i+1] != NULL)
                putchar(',');
        }
        putchar('\n');
    }
    endgrent();
    exit(0);
}

Każdy wiersz bazy grup reprezentuje jedną grupę. Pola rozdzielone są dwukropkami, i reprezentują poniższe dane.

Group Name
Nazwa grupy
Group Password
Zakodowane hasło grupy. W praktyce pole to nie jest nigdy wykorzystywane. Zwykle jest puste lub ma wartość `*'.
Group ID Number
Numeryczny identyfikator grupy. Liczba ta powinna być niepowtarzalna w obrębie pliku.
Group Member List
Lista rozdzielonych dwukropkami nazw użytkowników. Są oni członkami danej grupy. Większość systemów uniksowych zezwala, by użytkownicy byli członkami wielu grup równocześnie. Jeśli nasz system również na to pozwala, to odczyt `/dev/user' zwróci te numery identyfikatorów grup w polach $5 do $NF. (Zauważ, że `/dev/user' jest rozszerzeniem gawk; zob. 6.7. Specjalne nazwy plików w gawk.)

Oto, co może dać uruchomienie grcat:

$ grcat
-| wheel:*:0:arnold
-| nogroup:*:65534:
-| daemon:*:1:
-| kmem:*:2:
-| staff:*:10:arnold,miriam,andy
-| other:*:20:
...

Poniżej przedstawiono funkcje do uzyskiwania informacji z bazy grup. Mamy tu kilka funkcji, wzorowanych na funkcjach bibliotecznych C o tych samych nazwach.

# group.awk --- funkcje do obsługi pliku grup
# Arnold Robbins, 
 Adres poczty elektronicznej jest chroniony przed robotami spamującymi. W przeglądarce musi być włączona obsługa JavaScript, żeby go zobaczyć.
 , Public Domain
# May 1993

BEGIN    \
{
    # dopasuj to do własnego systemu
    _gr_awklib = "/usr/local/libexec/awk/"
}

function _gr_init(    oldfs, oldrs, olddol0, grcat, n, a, i)
{
    if (_gr_inited)
        return

    oldfs = FS
    oldrs = RS
    olddol0 = $0
    FS = ":"
    RS = "\n"

    grcat = _gr_awklib "grcat"
    while ((grcat | getline) > 0) {
        if ($1 in _gr_byname)
            _gr_byname[$1] = _gr_byname[$1] "," $4
        else
            _gr_byname[$1] = $0
        if ($3 in _gr_bygid)
            _gr_bygid[$3] = _gr_bygid[$3] "," $4
        else
            _gr_bygid[$3] = $0

        n = split($4, a, "[ \t]*,[ \t]*")
        for (i = 1; i <= n; i++)
            if (a[i] in _gr_groupsbyuser)
                _gr_groupsbyuser[a[i]] = \
                    _gr_groupsbyuser[a[i]] " " $1
            else
                _gr_groupsbyuser[a[i]] = $1

        _gr_bycount[++_gr_count] = $0
    }
    close(grcat)
    _gr_count = 0
    _gr_inited++
    FS = oldfs
    RS = oldrs
    $0 = olddol0
}

Reguła BEGIN przypisuje zmiennej prywatnej katalog, w którym przechowywany jest grcat. Ponieważ grcat wykorzystujemy jako pomoc dla procedury bibliotecznej awk, zdecydowaliśmy się umieścić go w `/usr/local/libexec/awk'. W swoim systemie możesz trzymać go w jakimś innym katalogu.

Podprogramy te mają ten sam schemat, co podprogramy bazy użytkowników (zob. 15.11. Czytanie bazy użytkowników). Zmienna _gr_inited wykorzystywana jest do zagwarantowania, że baza danych nie będzie przeglądana więcej niż raz. Funkcja _gr_init najpierw zachowuje FS, RS i $0, a następnie przypisuje FS i RS poprawne przy przeglądaniu informacji o grupach wartości.

Dane o grupach przechowywane są w kilku tablicach asocjacyjnych. Tablice indeksowane są nazwą grupy (_gr_byname), identyfikatorem grupy (_gr_bygid) i pozycją w bazie (_gr_bycount). Istnieje dodatkowa tablica indeksowana nazwą użytkownika (_gr_groupsbyuser), będąca listą rozdzielonych spacjami grup, do których należy każdy poszczególny użytkownik.

W przeciwieństwie do bazy użytkowników, można mieć w bazie wiele rekordów dla tej samej grupy. Jest to częste gdy grupa ma dużą ilość członków. Taka para wpisów może wyglądać tak:

tvpeople:*:101:johny,jay,arsenio
tvpeople:*:101:david,conan,tom,joan

Z tego powodu, _gr_init patrzy, czy nazwa lub identyfikator grupy nie były już widziane. Jeśli tak, to nazwy użytkowników są po prostu łączone z poprzednią listą użytkowników grupy. (W przedstawionym powyżej kodzie występuje pewnien trudno uchwytny problem. Załóżmy, że za pierwszym razem nie było żadnych nazw. Kod ten dodaje nazwy wraz z początkowym przecinkiem. Nie sprawdza też, czy istnieje $4.)

Na koniec, _gr_init zamyka potok do grcat, odtwarza FS, RS i $0, inicjuje _gr_count zerem (jest używane później), i czyni niezerowym _gr_inited.

function getgrnam(group)
{
    _gr_init()
    if (group in _gr_byname)
        return _gr_byname[group]
    return ""
}

Funkcja getgrnam pobiera nazwę grupy jako swój argument i, jeśli grupa ta istnieje, zwraca ją. W przeciwnym razie getgrnam zwraca łańcuch pusty.

function getgrgid(gid)
{
    _gr_init()
    if (gid in _gr_bygid)
        return _gr_bygid[gid]
    return ""
}

Funkcja getgrgid jest podobna, pobiera numeryczny identyfikator grupy i szuka informacji związanej z tym identyfikatorem.

function getgruser(user)
{
    _gr_init()
    if (user in _gr_groupsbyuser)
        return _gr_groupsbyuser[user]
    return ""
}

Funkcja getgruser nie ma odpowiednika w C. Pobiera nazwę użytkownika i zwraca listę grup, których jest on członkiem.

function getgrent()
{
    _gr_init()
    if (++_gr_count in _gr_bycount)
        return _gr_bycount[_gr_count]
    return ""
}

Funkcja getgrent przechodzi przez bazę, kolejno po jednym wpisie. Korzysta z _gr_count do zapamiętywania swego położenia na liście.

function endgrent()
{
    _gr_count = 0
}

endgrent zeruje _gr_count, tak że getgrent może rozpocząć ponownie od początku.

Jak w przypadku funkcji obsługi bazy użytkowników, każda z funkcji wywołuje _gr_init, by zainicjować tablice. Wykonywanie tych wywołań skutkuje dodatkowymi kosztami uruchamiania grcat tylko wtedy, gdy funkcje te zostaną użyte (w przeciwieństwie do przesunięcia ciała _gr_init do wnętrza reguły BEGIN).

Większość pracy stanowi przeszukiwanie bazy i tworzenie rozmaitych tablic asocjacyjnych. Funkcje, które wywołuje użytkownik, są same bardzo proste, zdając się na wykonanie zadania przez tablice asocjacyjne awk.

Opisane funkcje wykorzystywane są przez program id w 16.1.3. Wypisywanie informacji o użytkowniku.

15.13. Nazywanie zmiennych globalnych funkcji bibliotecznych

Z powodu sposobu, w jaki ewoluował język awk, zmienne są albo globalne (do użytku całego programu), albo lokalne (do użytku tylko konkretnej funkcji). Nie ma żadnego stanu pośredniego, analogicznego do zmiennych statycznych w C.

Funkcje biblioteczne często potrzebują zmiennych globalnych, które mogłyby wykorzystać do zachowania pomiędzy wywołaniami funkcji informacji o stanie. Na przykład, zmienna _opti występująca w getopt (zob. 15.10. Przetwarzanie opcji wiersza poleceń), czy tablica _tm_mies wykorzystywana przez mktime (zob. 15.7. Konwersja dat na znaczniki czasu). Zmienne takie nazywamy prywatnymi, gdyż jedynymi funkcjami, jakie potrzebują z nich korzystać są funkcje z biblioteki.

Przy pisaniu funkcji bibliotecznej, powinno się próbować wybrać nazwy swoich zmiennych prywatnych tak, by nie kolidowały one ze zmiennymi wykorzystywanymi albo przez inną funkcję biblioteczną, albo przez program główny użytkownika. Na przykład, nazwa taka jak `i' czy `j' nie jest dobrym wyborem, gdyż programy użytkownika często używają podobnych nazw zmiennych dla własnych celów.

Wszystkie programy przykładowe pokazane w niniejszym rozdziale zaczynały nazwy swoich zmiennych prywatnych od znaku podkreślenia (`_'). Użytkownicy na ogół nie stosują początkowych znaków podkreślenia w nazwach własnych zmiennych, więc konwencja ta od razu zmniejsza ryzyko przypadkowego współdzielenia nazwy zmiennej z programem użytkownika.

Dodatkowo, kilka spośród funkcji bibliotecznych stosuje przedrostek, wskazujący która funkcja czy zestaw funkcji wykorzystują dane zmienne. Na przykład, _tm_mies w mktime (zob. 15.7. Konwersja dat na znaczniki czasu) i _pw_byname w podprogramach bazy użytkowników (zob. 15.11. Czytanie bazy użytkowników). Zalecamy stosowanie się do tej konwencji, ponieważ zmniejsza ona nawet w większym stopniu ryzyko nieumyślnego konfliktu nazw. Zauważ, że konwencja ta może być wykorzystywana równie dobrze do nazw zmiennych, jak i do nazw funkcji prywatnych.

Mimo, iż mógłbym przepisać wszystkie procedury biblioteczne tak, by stosowały się do tej konwencji, nie zrobiłem tego, by pokazać jak ewoluował mój styl programowania w awk i zapewnić jakąś podstawę do rozważań.

Poczyńmy jeszcze ostatnią uwagę na temat nazywania zmiennych. Jeśli funkcja udostępnia zmienne globalne programowi głównemu, dobra konwencją jest rozpoczęcie nazwy zmiennej dużą literą. Na przykład, zmienne Opterr i Optind z getopt (zob. 15.10. Przetwarzanie opcji wiersza poleceń). Początkowa duża litera wskazuje, że jest to zmienna globalna, podczas gdy fakt, że cała nazwa zmiennej nie składa się z dużych liter wskazuje, że nie jest to jedna ze zmiennych wbudowanych awk, jak np. FS.

Ważne jest również, by wszystkie zmienne w funkcjach bibliotecznych, które nie muszą zachowywać stanu, były zadeklarowane jako lokalne. Jeśli się tego nie zrobi, zmienna może zostać przypadkowo użyta w programie użytkownika, co prowadzi do bardzo trudnych do wyśledzenia błędów.

function fun_bibl(x, y,    l1, l2)
{
    ...
    użycie zmiennej zmn  # zmn mogłaby być lokalna, ale
    ...                 # przez niedopatrzenie nie jest
    }

Inną konwencją, powszechną w społeczności Tcl, jest stosowanie pojedynczej tablicy asocjacyjnej do przechowywania wartości potrzebnych funkcji (funkcjom) bibliotecznym, czy inaczej "pakietowi". Znacząco zmniejsza to liczbę faktycznie wykorzystywanych nazw. Na przykład, funkcje opisane w 15.11. Czytanie bazy użytkowników, mogłyby używać PW_data["inited"], PW_data["total"], PW_data["count"] i PW_data["awklib"] zamiast _pw_inited, _pw_awklib, _pw_total i _pw_count.

Konwencje przedstawione w tej sekcji są dokładnie tym -- konwencjami. Nie musisz pisać programów w ten sposób, jedynie zalecamy, byś tak robił.

 


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.