Home Dokumentacje Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Instrukcje sterujące w akcjach
22 | 08 | 2019
Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Instrukcje sterujące w akcjach Drukuj

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

 


 

9. Instrukcje sterujące w akcjach

Instrukcje sterujące jak if, while, i tak dalej sterują przebiegiem wykonania programów awk. Większość instrukcji sterujących w awk wzorowana jest na podobnych instrukcjach z C.

Wszystkie instrukcje sterujące zaczynają się specjalnym słowem kluczowym, jak np. if czy while, które odróżnia je od wyrażeń prostych.

Wiele instrukcji sterujących zawiera inne instrukcje. Na przykład, instrukcja if zawiera inną instrukcję, która może być wykonana bądź nie. Taka zawarta instrukcja zwana jest ciałem (body) instrukcji sterującej. Jeżeli w ciele instrukcji sterującej chcemy zawrzeć więcej niż jedną instrukcję, to grupujemy je, za pomocą nawiasów klamrowych, w pojedynczą instrukcję złożoną, oddzielając je od siebie znakami nowej linii lub średnikami.

9.1. Instrukcja if-else

Instrukcja if-else jest w awk instrukcją "podejmowania decyzji". Wygląda tak:

if (warunek) ciało-if [else ciało-else]

Warunek jest wyrażeniem, które decyduje o tym, co zrobi reszta instrukcji. Jeżeli warunek jest prawdziwy, to wykonywane jest ciało-if; w przeciwnym razie, wykonywane jest ciało-else. Część else instrukcji jest opcjonalna. Warunek jest uważany za fałszywy jeśli jego wartością jest zero lub łańcuch pusty, w przeciwnym wypadku za prawdziwy.

Oto przykład:

if (x % 2 == 0)
    print "x jest parzyste"
else
    print "x jest nieparzyste"

W tym przykładzie, jeśli wyrażenie `x % 2 == 0' jest prawdziwe (to jest, wartość x dzieli się przez dwa bez reszty), to wykonywana jest pierwsza instrukcja print, w przeciwnym razie wykonywana jest druga instrukcja print.

Jeśli else pojawia się w tym samym wierszu, co ciało-if, a ciało-if nie jest instrukcją złożoną (tj. nie jest otoczone nawiasami klamrowymi), to ciało-if musi być oddzielone od else średnikiem. Dla ilustracji, napiszmy na nowo poprzedni przykład:

if (x % 2 == 0) print "x jest parzyste"; else
        print "x jest nieparzyste"

Jeżeli zapomnimy średnika `;', to awk nie będzie w stanie zinterpretować instrukcji i otrzymamy błąd składniowy.

W rzeczywistości powyższego przykładu nie napisalibyśmy w ten sposób, gdyż czytelnik mógłby nie zauważyć else gdyby nie było pierwszą rzeczą w wierszu.

9.2. Instrukcja while

W programowaniu słowo pętla (loop) oznacza część programu, która może być wykonana kolejno dwa lub więcej razy z rzędu.

Instrukcja while jest najprostszą instrukcją pętli w awk. Powtarza wykonywanie instrukcji dopóki warunek jest prawdziwy. Wygląda tak:

while (warunek)
  ciało

gdzie ciało jest instrukcją, którą nazywamy ciałem pętli, zaś warunek jest wyrażeniem decydującym o tym, jak długo ma działać pętla.

Pierwszą rzeczą, jaką robi instrukcja while jest sprawdzenie warunku. Jeżeli jest prawdziwy, to wykonuje ona instrukcję ciało. Po wykonaniu ciała pętli warunek jest testowany ponownie i jeżeli nadal jest prawdziwy, powtórnie wykonywane jest ciało. Proces ten powtarza się aż warunek przestanie być prawdziwy. Jeżeli warunek jest początkowo fałszywy, to ciało funkcji nie jest nigdy wykonywane, a awk kontynuuje pracę używając kolejnej po pętli instrukcji.

Ten przykład wypisuje pierwsze trzy pola każdego rekordu, po jednym w wierszu.

awk '{ i = 1
       while (i <= 3) {
           print $i
           i++
       }
}' inventory-shipped

Tu ciałem pętli jest ujęta w nawiasy klamrowe instrukcja złożona, zawierająca dwie instrukcje.

Nasza pętla działa tak: najpierw do i przypisywane jest jeden. Następnie, while sprawdza, czy i jest mniejsze lub równe trzy. Jest to prawdą gdy i równa się jeden, więc wypisywane jest i-te pole. Potem `i++' zwiększa wartość i o jeden i pętla się powtarza. Pętla kończy pracę, gdy i dojdzie do czterech.

Jak widać, pomiędzy warunkiem a ciałem nie jest wymagany znak nowej linii, ale zastosowanie go czyni program czytelniejszym, chyba że ciało jest instrukcją złożoną lub jest bardzo proste. Znak nowej linii po otwierającym nawiasie klamrowym, który rozpoczyna instrukcję złożoną, także nie jest konieczny, ale bez niego program byłby trudniejszy do czytania.

9.3. Instrukcja do-while

Pętla do jest odmianą instrukcji pętli while. Instrukcja do wykonuje jednokrotnie ciało, a następnie powtarza jego wykonywanie dopóki warunek jest prawdziwy. Wygląda tak:

do
  ciało
while (warunek)

Nawet jeżeli warunek jest fałszywy na starcie, ciało zostanie wykonane co najmniej raz (i tylko raz, chyba że wykonanie go spowoduje, że warunek stanie się prawdziwy). Inaczej jest z odpowiednią instrukcją while:

while (warunek)
  ciało

Ta instrukcja nie wykona ciała ani razu jeśli warunek, z którym rozpoczyna jest fałszywy.

Oto przykład instrukcji do:

awk '{ i = 1
       do {
          print $0
          i++
       } while (i <= 10)
}'

Ten program wypisuje każdy rekord dziesięć razy. Nie jest to zbyt realistyczny przykład, gdyż w tym przypadku równie dobrze wystarczyłoby zwykłe while. Odzwierciedla to rzeczywistą praktykę: faktyczna potrzeba użycia do występuje tylko sporadycznie.

9.4. Instrukcja for

Instrukcja for ułatwia zliczanie iteracji pętli. Ogólna postać instrukcji for wygląda tak:

for (inicjalizacja; warunek; inkrement)
  ciało

Części inicjalizacja, warunek i inkrement są dowolnymi wyrażeniami awk, zaś ciało oznacza dowolną instrukcję awk.

Instrukcja for zaczyna pracę od wykonania inicjalizacji. Następnie, dopóki warunek jest prawdziwy, powtarza wykonywanie ciała a potem inkrementu. Typowo inicjalizacja nadaje pewnej zmiennej wartość zero lub jeden, inkrement dodaje do niej jeden, a warunek porównuje ją z pożądaną liczbą iteracji.

Oto przykład instrukcji for:

awk '{ for (i = 1; i <= 3; i++)
          print $i
}' inventory-shipped

Wypisuje on pierwsze trzy pola każdego rekordu wejściowego, po jednym polu w wierszu.

W części inicjalizacja nie można nadać wartości więcej niż jednej zmiennej, chyba że posłużymy się przypisaniem wielokrotnym, jak np. `x = y = 0', które jest możliwe tylko gdy wszystkie wartości początkowe są równe. (Można jednak zainicjować dodatkowe zmienne pisząc przypisania do nich jako osobne instrukcje przed pętlą for.)

Obowiązuje to także dla części inkrement; chcąc zwiększać dodatkowe zmienne, musimy napisać osobne instrukcje na końcu pętli. W tym kontekście przydatne byłoby wyrażenie złożone z C, używające separatora przecinkowego, ale nie jest ono rozpoznawane w awk.

Najczęściej inkrement jest wyrażeniem inkrementującym, jak w przykładzie powyżej. Nie jest to jednak wymagane; może to być jakiekolwiek wyrażenie dowolnego typu. Na przykład, ta instrukcja wypisuje wszystkie potęgi dwójki między jeden a 100:

for (i = 1; i <= 100; i *= 2)
  print i

Można pominąć dowolne z trzech wyrażeń występujących w nawiasach po for jeśli ma ono nic nie robić. Zatem, `for (; x > 0;)' jest równoważne `while (x > 0)'. Jeżeli pominięto warunek, jest on traktowany jak prawda (true), w rezultacie powodując nieskończoną pętlę (tj. pętlę, która nigdy nie zakończy pracy).

W większości przypadków pętla for jest skrótem pętli while, jak pokazano tutaj:

inicjalizacja
while (warunek) {
  ciało

  inkrement
}

Jedyny wyjątek stanowi sytuacja, gdy wewnątrz pętli zastosowano instrukcję continue (zob. 9.6. Instrukcja continue). Zmiana w podany sposób instrukcji for na while może zmienić skutki instrukcji continue wewnątrz pętli.

Istnieje alternatywna wersja pętli loop przeznaczona do przechodzenia kolejno po wszystkich indeksach tablicy:

for (i in tablica)
    zrób coś z tablica[i]

Zob. 11.5. Przeglądanie wszystkich elementów tablicy, gdzie jest więcej o tej wersji pętli for.

Język awk oprócz instrukcji while ma instrukcję for, ponieważ często pętla for jest mniej pracochłonna przy wpisywaniu i bardziej naturalna w myśleniu o niej. W pętlach zliczanie liczby iteracji jest bardzo częste. Łatwiej jest myśleć o zliczaniu jako o części pętli niż jako o czymś, co ma być zrobione wewnątrz niej.

Następna sekcja zawiera bardziej skomplikowane przykłady pętli for.

9.5. Instrukcja break

Instrukcja break wyskakuje z najbardziej wewnętrznej obejmującej ją pętli for, while lub do. Poniższy program znajduje najmniejszy dzielnik liczby całkowitej, rozpoznaje też liczby pierwsze:

awk '# znajdź najmniejszy dzielnik liczby num
     { num = $1
       for (div = 2; div*div <= num; div++)
         if (num % div == 0)
           break
       if (num % div == 0)
         printf "Najmniejszym dzielnikiem %d jest %d\n", num, div
       else
         printf "%d jest liczbą pierwszą\n", num
     }'

Gdy resztą z dzielenia w pierwszej instrukcji if jest zero, awk natychmiast przerywa (breaks out) działanie zawierającej ją pętli for. Oznacza to, że awk przechodzi bezzwłocznie do instrukcji następującej po pętli i kontynuuje przetwarzanie. (Jest to całkiem inne niż instrukcja exit, która zatrzymuje cały program awk. Zob. 9.9. Instrukcja exit.)

Oto inny program równoważny poprzedniemu. Ilustruje sposób, w jaki warunek pętli for lub while może być równie dobrze zastąpiony przez break wewnątrz if:

awk '# znajdź najmniejszy dzielnik liczby num
     { num = $1
       for (div = 2; ; div++) {
         if (num % div == 0) {
           printf "Najmniejszym dzielnikiem %d jest %d\n", num, div
           break
         }
         if (div*div > num) {
           printf "%d jest liczbą pierwszą\n", num
           break
         }
       }
}'

Jak opisano powyżej, instrukcja break nie ma żadnego znaczenia, jeśli użyta jest poza ciałem pętli. Jednak, mimo iż nigdy tego nie udokumentowano, historyczne implementacje awk traktowały break poza pętlą tak, jakby była to instrukcja next (zob. 9.7. Instrukcja next). Najnowsze wersje uniksowego awk nie pozwalają już na taki sposób użycia. gawk obsługuje takie użycie break tylko jeśli w wierszu poleceń podano opcję `--traditional' (zob. 14.1. Opcje wiersza poleceń). W przeciwnym wypadku, zostanie ono potraktowane jako błąd, gdyż standard POSIX określa, że break powinno być stosowane wyłącznie wewnątrz ciała pętli (c.k.).

9.6. Instrukcja continue

Instrukcja continue, podobnie jak break, używana jest tylko wewnątrz pętli for, while i do. Pomija ona resztę ciała pętli, powodując natychmiastowe rozpoczęcie kolejnego cyklu pętli. Zwróć uwagę na różnicę w stosunku do break, które całkowicie wyskakuje z pętli.

Instrukcja continue w pętli for nakazuje awk przeskoczenie reszty ciała pętli i wznowienie jej wykonywania od wyrażenia inkrementacji instrukcji for. Ten fakt ilustruje poniższy program:

awk 'BEGIN {
     for (x = 0; x <= 20; x++) {
         if (x == 5)
             continue
         printf "%d ", x
     }
     print ""
}'

Program wypisuje wszystkie liczby od zera do 20, za wyjątkiem piątki, dla której printf jest pomijane. Ponieważ nie jest pomijany inkrement `x++', x nie pozostanie zaklinowane na pięciu. Inaczej niż w pętli loop powyżej jest w tej pętli while:

awk 'BEGIN {
     x = 0
     while (x <= 20) {
         if (x == 5)
             continue
         printf "%d ", x
         x++
     }
     print ""
}'

Ten program od momentu, gdy x otrzyma wartość pięć, będzie wykonywał wieczną pętlę.

Jak opisano powyżej, instrukcja continue nie ma żadnego znaczenia, jeśli użyta jest poza ciałem pętli. Jednak, mimo iż nigdy tego nie udokumentowano, historyczne implementacje awk traktowały continue poza pętlą tak, jakby była to instrukcja next (zob. 9.7. Instrukcja next). Najnowsze wersje uniksowego awk nie pozwalają już na taki sposób użycia. gawk obsługuje takie użycie continue tylko jeśli w wierszu poleceń podano opcję `--traditional' (zob. 14.1. Opcje wiersza poleceń). W przeciwnym wypadku, zostanie ono potraktowane jako błąd, gdyż standard POSIX określa, że continue powinno być stosowane wyłącznie wewnątrz ciała pętli (c.k.).

9.7. Instrukcja next

Instrukcja next wymusza na awk natychmiastowe przerwanie przetwarzania bieżącego rekordu i przejście do następnego. Znaczy to, że dla bieżącego rekordu nie będą wykonywane żadne dalsze reguły. Nie będzie też wykonywana reszta akcji aktualnej reguły.

Różni się to od skutków funkcji getline (zob. 5.8. Odczyt bezpośredni przez getline). getline również powoduje, że awk odczytuje natychmiast kolejny rekord, ale nie zmienia w żaden sposób przebiegu sterowania. Tak więc, z nowym rekordem wejściowym wykonywana jest dalsza część bieżącej akcji.

Na najwyższym poziomie wykonanie programu awk jest pętlą, która czyta rekord wejściowy i sprawdza go ze wzorcem każdej reguły. Jeśli myślimy o tej pętli jak o instrukcji for, której ciało zawiera reguły, to instrukcja next jest analogiczna do continue: przeskakuje na koniec ciała tej niejawnej pętli i wykonuje inkrementację (która czyta kolejny rekord).

Na przykład, jeżeli program awk działa tylko na rekordach o czterech polach, i nie chcemy by pracował błędnie gdy otrzyma złe wejście, możemy wykorzystać taką regułę blisko początku programu:

NF != 4 {
  err = sprintf("%s:%d: pominięty: NF != 4\n", FILENAME, FNR)
  print err > "/dev/stderr"
  next
}

tak, że kolejne reguły nie otrzymają nieprawidłowego rekordu. Komunikat o błędzie przekierowywany jest do standardowego strumienia błędów, tak jak powinny być kierowane komunikaty o błędach. Zob. 6.7. Specjalne nazwy plików w gawk.

Zgodnie ze standardem POSIX, jeśli instrukcja next jest użyta w regule BEGIN lub END to zachowanie się programu jest niezdefiniowane. gawk będzie traktował taką sytuację jako błąd składniowy. Mimo, że POSIX na to zezwala, niektóre inne implementacje awk nie pozwalają na umieszczanie instrukcji next wewnątrz ciała funkcji (zob. 13. Funkcje definiowane przez użytkownika). Tak jak każda inna instrukcja next, next we wnętrzu ciała funkcji czyta następny rekord i rozpoczyna jego przetwarzanie za pomocą pierwszej reguły programu.

Jeżeli instrukcja next spowoduje osiągnięcie końca wejścia, to zostanie wykonany kod z ewentualnych reguł END. Zob. 8.1.5. Wzorce specjalne BEGIN i END.

Uwaga! Niektóre implementacje awk generują błąd wykonania jeśli użyjemy instrukcji next wewnątrz funkcji zdefiniowanej przez użytkownika (zob. 13. Funkcje definiowane przez użytkownika). gawk nie ma takiego problemu.

9.8. Instrukcja nextfile

gawk udostępnia instrukcję nextfile, która jest zbliżona do instrukcji next. Jednak, zamiast zaprzestania przetwarzania bieżącego rekordu, instrukcja nextfile rozkazuje gawk przerwanie przetwarzania bieżącego pliku danych.

Podczas wykonania instrukcji nextfile, FILENAME aktualizowane jest nazwą następnego podanego w wierszu poleceń pliku danych, FNR ponownie otrzymuje początkową wartość jeden, zwiększane jest ARGIND, a przetwarzanie rozpoczyna się od nowa od pierwszej reguły programu. Zob. 10. Zmienne wbudowane.

Jeżeli nextfile spowoduje osiągnięcie końca wejścia, to zostanie wykonany kod ewentualnych reguł END. Zob. 8.1.5. Wzorce specjalne BEGIN i END.

Instrukcja nextfile jest rozszerzeniem gawk; nie jest ona (obecnie) dostępna w żadnej innej implementacji awk. Zob. 15.2. Implementacja nextfile jako funkcji, gdzie podano funkcję definiowaną przez użytkownika, jaką można wykorzystać do symulacji instrukcji nextfile.

Instrukcja nextfile może być użyteczna jeśli mamy do przetworzenia wiele plików danych, a spodziewamy się, że nie będziemy chcieć przetwarzać każdego rekordu w każdym pliku. Normalnie, w celu przejścia do następnego pliku danych, musielibyśmy kontynuować przeglądanie niepożądanych rekordów. Instrukcja nextfile realizuje to znacznie efektywniej.

Uwaga! Wersje gawk wcześniejsze niż 3.0 używały dwu słów (`next file') do zapisania instrukcji nextfile. W wersji 3.0 zmieniono to na jedno słowo, gdyż traktowanie słowa `file' było niespójne. Gdy pojawiało się po next, było słowem kluczowym. W przeciwnym razie, było zwykłym identyfikatorem. Stara składnia jest nadal akceptowana. gawk wygeneruje jednak komunikat ostrzegawczy, a obsługa next file ostatecznie przestanie być kontynuowana w przyszłych wersjach awk.

9.9. Instrukcja exit

Instrukcja exit powoduje, że awk natychmiast przestaje wykonywać bieżącą regułę i przestaje przetwarzać dane wejściowe; wszelkie pozostałe wejście jest ignorowane. Wygląda tak:

exit [kod powrotu]

Jeśli instrukcja exit zostanie wykonana z reguły BEGIN, to program natychmiast wstrzymuje wszelkie przetwarzanie. Nie są czytane żadne rekordy wejściowe. Jednak, jeśli istnieje reguła END, to jest ona wykonywana. (zob. 8.1.5. Wzorce specjalne BEGIN i END).

Jeżeli exit użyte jest jako część reguły END, powoduje natychmiastowe zatrzymanie programu.

Instrukcja exit nie będąca częścią ani reguły BEGIN ani END wstrzymuje wykonywanie ewentualnych dalszych automatycznych reguł dla bieżącego rekordu, pomija odczyt pozostałych rekordów wejściowych, i wykonuje regułę END, jeśli taka istnieje.

Jeżeli nie chcemy, by w takim przypadku reguła END wykonała swe zadanie, możemy przed instrukcją exit przypisać jakiejś zmiennej wartość niezerową i sprawdzić tę zmienną w regule END. Zob. 15.3. Asercje, gdzie jest przykład, który to robi.

Jeżeli podano argument instrukcji exit, to jego wartość jest wykorzystywana jako kod zakończenia procesu awk. Jeśli nie podano argumentu, exit zwraca kod zero (powodzenie). W przypadku, gdy podano argument pierwszej instrukcji exit, a następnie wywołano exit po raz drugi bez argumentu, używana jest poprzednio podana wartość zakończenia (c.k.).

Na przykład, powiedzmy, że wykryliśmy warunek wystąpienia błędu, który naprawdę nie wiemy jak obsłużyć. Zwyczajowo programy zgłaszają taką sytuację kończąc pracę z niezerowym kodem. Nasz program awk może to robić korzystając z instrukcji exit z niezerowym argumentem. Oto przykład:

BEGIN {
       if (("date" | getline date_now) <= 0) {
         print "Nie mogę pobrać daty systemowej" > "/dev/stderr"
         exit 1
       }
       print "bieżąca data to", date_now
       close("date")
}

 


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.