Home Dokumentacje Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Wyrażenia
06 | 12 | 2019
Efektywne programowanie w AWK - Podręcznik użytkownika GNU awk - Wyrażenia Drukuj

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

 


 

7. Wyrażenia

Wyrażenia są podstawowymi elementami konstrukcyjnymi wzorców i akcji awk. Wyrażenie rozwija się w wartość, którą można wypisać, porównywać, przechować w zmiennej czy przesłać do funkcji. Dodatkowo, wyrażenie, za pomocą operatora przypisania, może przypisać nową wartość zmiennej lub polu.

Wyrażenie może służyć jako samodzielny wzorzec lub instrukcja akcji. Większość pozostałych rodzajów instrukcji zawiera jedno lub więcej wyrażeń, określających dane na których mają działać. Jak w innych językach, wyrażenia w awk zawierają zmienne, odwołania do tablic, stałe i wywołania funkcji, jak również ich kombinacje z rozmaitymi operatorami.

7.1. Wyrażenia stałe

Najprostszym rodzajem wyrażenia jest stała, która ma zawsze tę samą wartość. Są trzy typy stałych: stałe numeryczne (liczbowe), stałe łańcuchowe i stałe wyrażenia regularne.

7.1.1. Stałe numeryczne i łańcuchowe

Stała numeryczna oznacza liczbę. Może to być liczba całkowita, ułamek dziesiętny lub liczba w notacji naukowej (wykładniczej).(7) Oto kilka przykładów stałych numerycznych, wszystkich o tej samej wartości:

105
1.05e+2
1050e-1

Stała łańcuchowa składa się z ciągu znaków ujętego w cudzysłowy. Na przykład:

"papuga"

reprezentuje łańcuch, którego zawartością jest `papuga'. Łańcuchy w gawk mogą mieć dowolną długość i mogą zawierać dowolny z możliwych ośmiobitowych znaków ASCII, łącznie z ASCII NUL (znak o kodzie zero). Inne implementacje awk mogą mieć pewne trudności z niektórymi kodami znaków.

7.1.2. Stałe regexp

Stałe wyrażenie regularne (stała regexp) jest opisem wyrażenia regularnego ujętym między ukośniki, jak /^początek i koniec$/. Większość wyrażeń regularnych wykorzystywanych w programach awk jest stałymi, ale operatory dopasowania `~' i `!~' mogą też dopasowywać wyliczane lub "dynamiczne" wyrażenia regularne (które są po prostu zwykłymi łańcuchami czy zmiennymi, zawierającymi wyrażenie regularne).

7.2. Używanie stałych regexp

Użyte po prawej stronie operatorów `~' lub `!~' wyrażenie regularne stałe oznacza po prostu wyrażenie regularne jakie ma zostać dopasowane.

Stałe wyrażenia regularne (jak /foo/) mogą być stosowane jako wyrażenia proste. Gdy pojawia się samo wyrażenie regularne stałe, ma ono to samo znaczenie jakby pojawiło się we wzorcu, tj. `($0 ~ /foo/)' (c.k.) (zob. 8.1.3. Wyrażenia jako wzorce). Oznacza to, że te dwa fragmenty kodu,

if ($0 ~ /barfly/ || $0 ~ /camelot/)
    print "znaleziono"

i

if (/barfly/ || /camelot/)
    print "znaleziono"

są dokładnie równoważne.

Pewną dość dziwaczną konsekwencją tej zasady jest to, że poniższe wyrażenie logiczne jest poprawne, choć nie robi tego, co prawdopodobnie miał na myśli użytkownik:

# zauważ, że /foo/ jest po lewej stronie ~
if (/foo/ ~ $1) print "znaleziono foo"

Ten kod "oczywiście" sprawdza, czy $1 pasuje do wyrażenia regularnego /foo/. W rzeczywistości jednak, wyrażenie `/foo/ ~ $1' oznacza faktycznie `($0 ~ /foo/) ~ $1'. Inaczej mówiąc, najpierw dopasowuje rekord wejściowy do wyrażenia regularnego /foo/. Wynikiem będzie albo zero albo jeden, zależnie od powodzenia lub porażki dopasowania. Następnie zostanie wykonane dopasowanie tego wyniku do pierwszego pola rekordu.

Ponieważ jest mało prawdopodobne, byśmy kiedykolwiek chcieli robić taki rodzaj sprawdzenia, gawk wyśle ostrzeżenie widząc taką konstrukcję w programie.

Inną konsekwencją omawianej zasady jest to, że instrukcja przypisania

pasuje = /foo/

nada zmiennej pasuje wartość zero albo jeden, zależnie od zawartości bieżącego rekordu wejściowego.

Ta cecha języka nie była nigdy dobrze udokumentowana aż do specyfikacji POSIX.

Wyrażenia regularne stałe są też wykorzystywane jako pierwszy argument funkcji gensub, sub i gsub, i jako drugi argument funkcji match (zob. 12.3. Funkcje wbudowane działające na łańcuchach). Współczesne implementacje awk, łącznie z gawk, pozwalają, by trzecim argumentem split było stałe wyrażenie regularne, podczas gdy niektóre starsze implementacje na to nie pozwalają (c.k.).

Próba użycia stałego wyrażenia regularnego jako argumentu funkcji zdefiniowanej przez użytkownika może prowadzić do zamieszania. (zob. 13. Funkcje definiowane przez użytkownika). Na przykład:

function mysub(pat, repl, str, global)
{
    if (global)
        gsub(pat, repl, str)
    else
        sub(pat, repl, str)
    return str
}

{
    ...
    tekst = "cześć! cześć do siebie!"
    mysub(/cześć/, "sie ma", tekst, 1)
    ...
}

W tym przykładzie programista chce przekazać stałe wyrażenie regularne do funkcji użytkownika mysub, która z kolei prześle je sub lub gsub. Jednak faktycznie parametr pat będzie albo jedynką albo zerem, w zależności od tego czy $0 pasuje do /cześć/ czy nie.

Ponieważ to nieprawdopodobne, byśmy kiedykolwiek chcieli przekazać wartość prawdy w ten sposób, gawk widząc stałe wyrażenie regularne użyte jako parametr funkcji użytkownika wyśle ostrzeżenia.

7.3. Zmienne

Zmienne stanowią metodę przechowywania wartości w pewnym punkcie programu do późniejszego użytku w innym miejscu programu. Można nimi manipulować wewnątrz całego tekstu programu, można im też przypisać wartości w wierszu poleceń awk.

7.3.1. Używanie zmiennych w programie

Zmienne umożliwiają nadanie nazw wartościom i późniejsze się do nich odwoływanie. Widzieliśmy je już w wielu przykładach. Nazwa zmiennej musi być ciągiem liter, cyfr i znaków podkreślenia, ale nie może zaczynać się od cyfry. W nazwach zmiennych wielkość liter jest znacząca: a i A to różne zmienne.

Sama nazwa zmiennej jest poprawnym wyrażeniem. Reprezentuje aktualną wartość zmiennej. Nowa wartości nadawane są zmiennym za pomocą operatorów przypisania, operatorów inkrementacji i operatorów dekrementacji. Zob. 7.7. Wyrażenia przypisania.

Kilka zmiennych ma specjalne, wbudowane znaczenie, tak jak FS, separator pól, czy NF, liczba pól w bieżącym rekordzie wejściowym. Zob. 10. Zmienne wbudowane, gdzie podano ich listę. Zmienne wbudowane mogą być używane jak wszystkie inne zmienne, i jak innym zmiennym można im przypisywać wartości. Jednak wartości tych zmiennych są także wykorzystywane i zmieniane automatycznie przez awk. Nazwy wszystkich zmiennych wbudowanych są w całości zbudowane z dużych liter.

Zmiennym można przypisywać wartości numeryczne lub łańcuchowe. Domyślnie, zmienne inicjowane są łańcuchem pustym, który po konwersji na liczbę jest zerem. W awk nie ma potrzeby jawnego "inicjowania" każdej ze zmiennych, w sposób, jaki robi się to w C i większości innych tradycyjnych języków.

7.3.2. Przypisywanie zmiennych w wierszu poleceń

Zmiennej można nadać wartość dołączając przy wywoływaniu awk przypisanie zmiennej do argumentów wiersza poleceń. (zob. 14.2. Inne argumenty wiersza poleceń). Takie przypisanie ma następującą postać:

zmienna=tekst

W ten sposób można nadać wartość zmiennej albo na początku pracy awk albo pomiędzy plikami wejściowymi.

Przypisanie można poprzedzić opcją `-v', w ten sposób:

-v zmienna=tekst

wówczas zmienna ma nadawaną wartość na samym początku, nawet przed wykonaniem reguł BEGIN. Opcja `-v' i jej przypisanie muszą poprzedzać zarówno wszystkie argumenty podające nazwy plików jak i tekst programu. (Zob. 14.1. Opcje wiersza poleceń, gdzie jest więcej o opcji `-v'.)

W przeciwnym razie, przypisanie do zmiennej wykonywane jest w momencie określonym jego pozycją wśród argumentów plików wejściowych: po przetworzeniu pliku wejściowego będącego poprzednim argumentem. Na przykład:

awk '{ print $n }' n=4 inventory-shipped n=2 BBS-list

wypisuje wartość pola numer n dla wszystkich rekordów wejściowych. Przed odczytem pierwszego pliku wiersz poleceń nadaje zmiennej n wartość cztery. Powoduje to, że z każdego wiersza pliku `inventory-shipped' wypisane będzie czwarte pole. Po zakończeniu pierwszego pliku, ale przed rozpoczęciem drugiego, n jest przypisywane dwa, więc z wierszy `BBS-list' jest wypisywane drugie pole.

$ awk '{ print $n }' n=4 inventory-shipped n=2 BBS-list
-| 15
-| 24
...
-| 555-5553
-| 555-3412
...

Argumenty wiersza poleceń są w tablicy o nazwie ARGV udostępniane programowi awk do bezpośredniego zbadania (zob. 10.3. Używanie ARGC i ARGV).

awk przetwarza wartości przypisań z wiersza poleceń wykorzystując sekwencje specjalne (c.k.) (zob. 4.2. Sekwencje specjalne).

7.4. Konwersja łańcuchów i liczb

Jeżeli kontekst programu awk tego wymaga, łańcuchy są przekształcane na liczby, a liczby na łańcuchy. Na przykład, jeżeli okaże się, że w wyrażeniu `foo + bar' wartość `foo' lub `bar' jest łańcuchem, to przed wykonaniem dodawania będzie ona konwertowana na liczbę. Jeśli w konkatenacji łańcuchów pojawią się liczby, to zostaną przekształcone na łańcuchy. Rozważmy poniższe:

dwa = 2; trzy = 3
print (dwa trzy) + 4

Wypisze to wartość (numeryczną) 27. Liczbowe wartości zmiennych dwa i trzy są przekształcane na łańcuchy i sklejane, a łańcuch wynikowy przekształcany z powrotem na liczbę (23), do której jest następnie dodawane cztery.

Jeżeli z jakiegoś powodu trzeba wymusić konwersję liczby na łańcuch, sklejamy z nią pusty łańcuch, "". W celu wymuszenia konwersji łańcucha na liczbę, dodajemy do niego zero.

Łańcuch jest przekształcany na liczbę przez interpretację jego ewentualnego numerycznego przedrostka jako liczby: "2.5" konwertowane jest na 2.5, "1e3" na 1000, a "25fix" ma numeryczną wartość 25. Łańcuchy, których nie mogą być zinterpretowane jako poprawne liczby są konwertowane na zero.

Dokładny sposób, w jaki liczby przekształcane są na łańcuchy zależy od zmiennej wbudowanej awk o nazwie CONVFMT (zob. 10. Zmienne wbudowane). Liczby konwertowane są za pomocą funkcji sprintf (zob. 12.3. Funkcje wbudowane działające na łańcuchach) z CONVFMT jako specyfikatorem formatu.

Domyślną wartością CONVFMT jest "%.6g", co wypisuje wartość z co najmniej sześcioma cyframi znaczącymi. W niektórych zastosowaniach zechcemy ją zmienić, by określić większą dokładność. Na większości współczesnych maszyn trzeba wypisać 17 cyfr by uchwycić dokładnie wartość liczby podwójnej precyzji.

Jeżeli CONVFMT przypiszemy łańcuch nie mówiący sprintf jak w użyteczny sposób sformatować liczby zmiennoprzecinkowe, to mogą pojawić się dziwne wyniki. Na przykład, jeżeli zapomnimy znaku `%' w formacie, to wszystkie liczby będą konwertowane na ten sam stały łańcuch.

Jako przypadek szczególny, jeśli liczba jest całkowitą, to wynik jej przekształcenia na łańcuch jest zawsze całkowitą, bez względu na to, jaka jest wartość CONVFMT. Przy poniższym fragmencie kodu:

CONVFMT = "%2.2f"
a = 12
b = a ""

b ma wartość "12", nie "12.00" (c.k.).

Przed standardem POSIX, awk podawał, że do konwersji liczb na łańcuchy jest wykorzystywana wartość OFMT. OFMT określa format wyjściowy stosowany przy wypisywaniu liczb za pomocą print. W celu oddzielenia semantyki konwersji od semantyki wypisywania wprowadzono zmienną CONVFMT. CONVFMT i OFMT mają tę samą wartość domyślną: "%.6g". W ogromnej większości przypadków stare programy awk nie zmienią swego zachowania. Jednak takie zastosowanie OFMT jest czymś, o czym warto pamiętać, jeśli chcemy przenosić programy na inne implementacje awk. Zalecamy, by zamiast zmieniać własne programy, po prostu przenieść sam gawk! Zob. 6.1. Instrukcja print, gdzie jest więcej o instrukcji print.

7.5. Operatory arytmetyczne

Język awk przy obliczaniu wyrażeń stosuje standardowe operatory arytmetyczne. Wszystkie z nich podlegają normalnym regułom priorytetu i działają tak, jak jakbyśmy tego oczekiwali. Operacje arytmetyczne wykonywane są z wykorzystaniem zmiennopozycyjnej podwójnej precyzji, co rodzi zwykłe kłopoty z niedokładnością i wyjątkami.(8)

Oto plik `grades' zawierający listę nazw studentów i ich wyniki z trzech testów (to mała klasa):

Pat   100 97 58
Sandy  84 72 93
Chris  72 92 89

Ten program bierze plik `grades' i wypisuje średnią punktów.

$ awk '{ sum = $2 + $3 + $4 ; avg = sum / 3
>        print $1, avg }' grades
-| Pat 85
-| Sandy 83
-| Chris 84.3333

Poniższa tabela wyszczególnia operatory arytmetyczne w języku awk, od najwyższego do najniższego priorytetu:

- x
Negacja.
+ x
Jednoargumentowy plus. Wyrażenie jest konwertowane na liczbę.
x ^ y
x ** y
Potęgowanie: x podniesione do potęgi y. `2 ^ 3' ma wartość osiem. Ciąg znaków `**' jest równoważny `^'. (Standard POSIX dla potęgowania podaje tylko używanie `^'.)
x * y
Mnożenie.
x / y
Dzielenie. Ponieważ wszystkie liczby w awk są liczbami zmiennoprzecinkowymi, wynik nie jest zaokrąglany do całkowitej: `3 / 4' ma wartość 0.75.
x % y
Reszta z dzielenia. Iloraz jest zaokrąglany w dół do całkowitej, mnożony przez y i ten wynik jest odejmowany od x. Operacja ta jest czasem nazywana "obcięcie-modulo". Poniższa relacja jest zawsze zachowana:
b * int(a / b) + (a % b) == a
Pewnym, być może niepożądanym, efektem tej definicji reszty z dzielenia jest to, że x % y jest ujemne jeśli x jest ujemne. Zatem,
-17 % 8 = -1
W innych implementacjach awk, uzyskiwany znak reszty z dzielenia może być zależny od architektury komputera.
x + y
Dodawanie.
x - y
Odejmowanie.

W celu zachowania maksymalnej przenośności, nie należy stosować operatora `**'.

Jednoargumentowy plus i minus mają ten sam priorytet, operatory multiplikatywne mają wszystkie ten sam priorytet, dodawanie i odejmowanie mają ten sam priorytet.

7.6. Konkatenacja łańcuchów

Wtedy wyglądało to na dobry pomysł.

Brian Kernighan

 

Istnieje tylko jedna operacja łańcuchowa: konkatenacja (złączenie). Nie ma ona żadnego konkretnego operatora, który by ją reprezentował. Zamiast tego, sklejanie łańcuchów wykonywane jest przez zapisanie jednego wyrażenia obok drugiego, bez żadnego operatora. Na przykład:

$ awk '{ print "Pole numer jeden: " $1 }' BBS-list
-| Pole numer jeden: aardvark
-| Pole numer jeden: alpo-net
...

Bez spacji po `:' w stałej łańcuchowej, składowe wiersza nie byłyby rozdzielone. Na przykład:

$ awk '{ print "Pole numer jeden::" $1 }' BBS-list
-| Pole numer jeden::aardvark
-| Pole numer jeden::alpo-net
...

Ponieważ konkatenacja łańcuchów nie ma jawnego operatora, często konieczne jest upewnienie się, że dzieje się to czego chcemy. Można je uzyskać otaczając złączane elementy nawiasami. Na przykład, poniższy fragment kodu nie zlepia file i name jak moglibyśmy się spodziewać:

file = "file"
name = "name"
print "coś istotnego" > file name

Konieczne jest wykorzystanie poniższego:

print "coś istotnego" > (file name)

Zalecamy użycie nawiasów wokół konkatenacji we wszystkich kontekstach oprócz najpowszechniejszych (jak po prawej stronie `=').

7.7. Wyrażenia przypisania

Przypisanie jest wyrażeniem, które zapisuje w zmiennej nową wartość. Na przykład, przypiszmy zmiennej z wartość jeden:

z = 1

Po wykonaniu tego wyrażenia zmienna z ma wartość jeden. Jakakolwiek była stara wartość zmiennej z przed przypisaniem, jest ona zapominana.

Przypisania składują też wartości łańcuchowe. Na przykład to zapamięta wartość "this food is good" w zmiennej message:

thing = "food"
predicate = "good"
message = "this " thing " is " predicate

(Ilustruje to również konkatenację łańcuchów.)

Znak `=' nazywamy operatorem przypisania. Jest to najprostszy operator przypisania, gdyż wartość operandu po prawej stronie jest składowana bez zmian.

Większość operatorów (dodawanie, konkatenacja, i tak dalej) nie daje żadnych skutków poza obliczeniem wartości. Jeżeli zignorujemy tę wartość, to równie dobrze moglibyśmy nie stosować operatora. Operator przypisania jest inny: tworzy wartość, lecz nawet jeśli ją ignorujemy, to przypisanie nadal daje znać o sobie zmieniając zawartość zmiennej. Nazywamy to skutkiem ubocznym.

Lewostronny operand przypisania nie musi być zmienną (zob. 7.3. Zmienne); może być również polem (zob. 5.4. Zmiana zawartości pól) lub elementem tablicy (zob. 11. Tablice w awk). Wszystkie one zwane są lwartościami, co znaczy że mogą pojawić się po lewej stronie operatora przypisania. Operand prawostronny może być dowolnym wyrażeniem. Tworzy ono nową wartość, którą przypisanie zapamiętuje w zadanej zmiennej, polu czy elemencie tablicy. (Wartości takie są zwane rwartościami, od ang."right-hand", prawostronny.)

Należy pamiętać, że zmienne nie mają stałego typu. Typ zmiennej jest po prostu typem wartości, jaką ona w danej chwili akurat przechowuje. W poniższym fragmencie programu zmienna foo ma najpierw wartość numeryczną, a potem łańcuchową:

foo = 1
print foo
foo = "bar"
print foo

W chwili, gdy drugie przypisanie nadaje foo wartość łańcuchową, to, że uprzednio miała ona wartość numeryczną jest zapominane.

Wartości łańcuchowe nierozpoczynające się cyfrą mają numeryczną wartość zero. Po wykonaniu tego kodu, wartością foo jest pięć:

foo = "a string"
foo = foo + 5

(Zwróć uwagę, że używanie zmiennej jako liczby a później jako łańcucha jest mylące i świadczy o kiepskim stylu programowania. Powyższe przykłady pokazują jak działa awk, a nie jak powinno się pisać programy!)

Przypisanie jest wyrażeniem, zatem posiada wartość: tę samą wartość, która jest przypisywana. Stąd też, `z = 1' jako wyrażenie ma wartość jeden. Skutkiem tego jest fakt, że można zapisać razem kilka przypisań:

x = y = z = 0

zapamiętuje wartość zero we wszystkich trzech zmiennych. Dzieje się tak dlatego, że wartość `z = 0', która jest zerem, jest zapamiętywana w y, a następnie wartość `y = z = 0', która jest zerem, zapamiętywana jest w x.

Przypisania można użyć w każdym miejscu, gdzie oczekiwane jest wyrażenie. Na przykład, poprawne jest napisanie `x != (y = 1)' w celu nadania y wartości jeden i sprawdzenia czy x jest równe jeden. Ten styl prowadzi jednak do tego, że programy są trudne do czytania. Nie powinno się używać takich zagnieżdżonych przypisań, poza programami do jednorazowego wykorzystania.

Oprócz `=', istnieje kilka innych operatorów przypisania, wykonujących działania arytmetyczne na starej wartości zmiennej. Na przykład, operator `+=' oblicza nową wartość przez dodanie prawostronnej wartości do starej wartości zmiennej. Poniższe przypisanie, zatem, dodaje pięć do wartości foo:

foo += 5

Jest to równoważne poniższemu:

foo = foo + 5

Stosujemy to rozwiązanie, przy którym sens programu jest klarowniejszy.

Istnieją sytuacje, w których stosowanie `+=' (lub innego operatora przypisania) nie jest tym samym, co zwykłe powtórzenie lewego operandu w wyrażeniu prawostronnym. Na przykład:

# Dzięki Pat Rankin za przykład
BEGIN  {
    foo[rand()] += 5
    for (x in foo)
       print x, foo[x]

    bar[rand()] = bar[rand()] + 5
    for (x in bar)
       print x, bar[x]
}

Indeksy bar są na pewno różne, ponieważ rand zwróci różne wartości przy każdym jej wywołaniu. (Tablice i funkcja rand nie były jeszcze omawiane. Zob. 11. Tablice w awk, i zobacz 12.2. Wbudowane funkcje numeryczne). Ten przykład ilustruje ważny fakt dotyczący operatorów przypisania: lewostronne wyrażenie jest wyliczane tylko raz.

To, czy obliczane jest najpierw wyrażenie lewostronne czy też prawostronne, zależy od implementacji. Rozważmy przykład:

i = 1
a[i += 2] = i + 1

Wartością a[3] może być albo dwa albo cztery.

Oto tabela operatorów przypisania arytmetycznego. W każdym przypadku prawostronny operand jest wyrażeniem, którego wartość jest przekształcana na liczbę:

lwartość += inkrement
Dodaje inkrement do wartości lwartości tworząc nową wartość lwartości.
lwartość -= dekrement
Odejmuje dekrement od wartości lwartości.
lwartość *= współczynnik
Mnoży wartość lwartości przez współczynnik.
lwartość /= dzielnik
Dzieli wartość lwartości przez dzielnik.
lwartość %= współczynnik
Przypisuje lwartości resztę z dzielenia jej przez współczynnik.
lwartość ^= potęga
lwartość **= potęga
Podnosi lwartość do potęgi potęga. (POSIX podaje tylko operator `^='.)

W celu zachowania maksymalnej przenośności nie należy stosować operatora `**='.

7.8. Operatory inkrementacji i dekrementacji

Operatory inkrementacji i dekrementacji powiększają lub pomniejszają wartość zmiennej o jeden. Można zrobić to samo wykorzystując operator przypisania, więc operatory inkrementacji nie dodają językowi awk żadnej dodatkowej zdolności. Są jednak wygodnymi skrótami dla bardzo typowych operacji.

Operator dodający jeden zapisujemy `++'. Może służyć do powiększania wartości zmiennej zarówno przed jak i po pobraniu jej wartości.

Powiększenie wartości zmiennej v przed zwróceniem wyniku (pre-inkrementację) zapisujemy `++v'. Dodaje to jeden do wartości v i ta nowa wartość jest równocześnie wartością wyrażenia. Przypisanie `v += 1' jest całkowicie równoważne.

Zapis `++' po zmiennej określa post-inkrementację. Powiększa ona tak samo wartość zmiennej; różnicą jest to, że wartością samego wyrażenia inkrementacji jest stara wartość zmiennej. Zatem, jeśli foo ma wartość cztery, to wyrażenie `foo++' ma wartość cztery, ale zmienia ono wartość foo na pięć.

Post-inkrementacja `foo++' jest prawie równoważna zapisowi `(foo += 1) - 1'. Nie jest dokładnie równoważna, gdyż wszystkie liczby w awk są zmiennoprzecinkowe: w arytmetyce zmiennoprzecinkowej `foo + 1 - 1' niekoniecznie równa się foo. Różnica jest jednak znikoma dopóki ograniczamy się do liczb, które są dość małe (mniejsze niż 10e12).

Inkrementowana może być dowolna lwartość. Pola i elementy tablic są inkrementowane tak samo jak zmienne. (Jeżeli chcemy równocześnie odwołać się do pola i powiększyć zmienną, możemy zastosować `$(i++)'. Nawiasy są niezbędne z powodu priorytetu operatora wskazania pola, `$'.)

Operator dekrementacji `--' działa tak samo jak `++', z wyjątkiem tego, że odejmuje jeden zamiast dodawać. Podobnie jak `++', może być stosowany przed lwartością do pre-dekrementacji lub po niej do post-dekrementacji.

A oto podsumowanie wyrażeń inkrementacji i dekrementacji.

++lwartość
To wyrażenie inkrementuje lwartość a nowa wartość staje się wartością wyrażenia.
lwartość++
To wyrażenie inkrementuje lwartość, lecz wartością wyrażenia jest stara wartość lwartości.
--lwartość
Podobne do `++lwartość', ale zamiast dodawania, odejmuje. Dekrementuje lwartość i jako wynik dostarcza uzyskaną wartość.
lwartość--
Podobne do `lwartość++', ale zamiast dodawania, odejmuje. Dekrementuje lwartość. Wartością wyrażenia jest stara wartość lwartości.

7.9. Prawda i fałsz w awk

Wiele języków programowania ma specjalną reprezentację pojęć "prawdy" i "fałszu". Języki takie korzystają zwykle ze specjalnych stałych true i false, lub, być może, ich równoważników pisanych dużymi literami.

awk jest inny. Zapożycza z C bardzo prostą koncepcję wartości prawdy i fałszu. W awk, dowolna niezerowa wartość numeryczna, lub niepusty łańcuch są prawdziwe. Każda inna wartość (zero lub łańcuch zerowej długości, "") jest fałszywa. Poniższy program wypisze trzykrotnie `Dziwna wartość prawdziwa':

BEGIN {
   if (3.1415927)
       print "Dziwna wartość prawdziwa"
   if ("Four Score And Seven Years Ago")
       print "Dziwna wartość prawdziwa"
   if (j = 57)
       print "Dziwna wartość prawdziwa"
}

Zaskakująca jest konsekwencja reguły "niezerowe lub niepuste": stała łańcuchowa "0" jest w rzeczywistości prawdziwa, bo jest niepusta (c.k.).

7.10. Typy zmiennych i wyrażenia porównania

Ten przewodnik jest rozstrzygający. Rzeczywistość często jest nieścisła.
Autostopem przez Galaktykę

 

Inaczej niż w innych językach programowania, zmienne awk nie mają stałego typu. Mogą, zamiast tego, być liczbą albo łańcuchem, zależnie od wartości, jaka jest do nich przypisana.

Standard POSIX z roku 1992 wprowadził pojęcie łańcucha liczbowego, który jest po prostu łańcuchem wyglądającym jak liczba, na przykład, " +2". Pojęcie to jest wykorzystywane do wyznaczania typu zmiennej.

Typ zmiennej jest istotny, gdyż typy dwu zmiennych decydują o sposobie, w jaki są one porównywane.

W gawk kontrola typu zmiennej następuje według poniższych reguł.

  1. Literał numeryczny lub wynik operacji numerycznej ma atrybut numeryczny.
  2. Literał łańcuchowy lub wynik operacji łańcuchowej ma atrybut łańcuchowy.
  3. Pola, wejście getline, FILENAME, elementy ARGV, elementy ENVIRON oraz elementy tablicy utworzonej przez split, które są łańcuchami liczbowymi mają atrybut łańcucha liczbowego (strnum). W przeciwnym przypadku mają atrybut łańcuchowy. Niezainicjowane zmienne mają również atrybut łańcucha liczbowego.
  4. Atrybuty przenoszą się poprzez przypisania, ale nie są zmieniane przez żadne użycie.

Ostatnia reguła jest szczególnie ważna. W poniższym programie, a ma typ numeryczny, nawet mimo tego, że jest później używana w operacji łańcuchowej.

BEGIN {
         a = 12.345
         b = a " jest ładną liczbą"
         print b
}

Przy porównywaniu dwu argumentów może być użyte albo porównanie łańcuchowe albo numeryczne, zależnie od ich typu. Wykonywane jest ono zgodnie z poniższą, symetryczną macierzą:

Najprościej rzecz biorąc, wejście użytkownika wyglądające na numeryczne, i tylko wejście, powinno być traktowane jako numeryczne, nawet jeśli faktycznie jest zbudowane ze znaków, i z tego powodu jest też łańcuchem.

Wyrażenia porównania porównują łańcuchy lub liczby co do relacji między nimi, jak np. równość. Są one zapisywane przy użyciu operatorów relacji, będących nadzbiorem analogicznych operatorów występujących w C. Oto ich tabela:

x < y
Prawda jeśli x jest mniejsze od y.
x <= y
Prawda jeśli x jest mniejsze lub równe y.
x > y
Prawda jeśli x jest większe od y.
x >= y
Prawda jeśli x jest większe lub równe y.
x == y
Prawda jeśli x jest równe y.
x != y
Prawda jeśli x jest nie jest równe y.
x ~ y
Prawda jeśli łańcuch x pasuje do wyrażenia regularnego oznaczanego przez y.
x !~ y
Prawda jeśli łańcuch x nie pasuje do wyrażenia regularnego oznaczanego przez y.
indeks in tabl
Prawda jeśli tablica tabl posiada element o indeksie indeks.

Wyrażenia porównania mają wartość jeden jeśli dają prawdę i zero jeśli fałsz.

Przy porównywaniu operandów różnych typów operandy numeryczne przekształcane są na łańcuchy przy wykorzystaniu wartości CONVFMT (zob. 7.4. Konwersja łańcuchów i liczb).

Łańcuchy porównywane są przez porównanie pierwszego znaku każdego z nich, następnie drugiego znaku w każdym i tak dalej. Zatem "10" jest mniejsze niż "9". Jeżeli mamy dwa łańcuchy, z których jeden jest przedrostkiem drugiego, to krótszy jest mniejszy od dłuższego. Stąd też, "abc" jest mniejsze niż "abcd".

Bardzo łatwo jest przypadkowo błędnie napisać operator `==', i ominąć jeden ze znaków równości `='. Wynik jest nadal poprawnym kodem awk, ale program nie będzie robił tego, co mieliśmy na myśli:

if (a = b)   # oops! powinno być a == b
   ...
else
   ...

Test if powiedzie się zawsze, chyba że b będzie akurat zerem lub łańcuchem pustym. Ponieważ oba operatory są podobne, ten rodzaj błędu jest bardzo trudny do zauważenia przy sprawdzaniu kodu źródłowego.

Pokażemy kilka przykładowych wyrażeń, jak awk je porównuje, i jaki jest wynik porównania.

1.5 <= 2.0
porównanie numeryczne (prawda)
"abc" >= "xyz"
porównanie łańcuchowe (fałsz)
1.5 != " +2"
porównanie łańcuchowe (prawda)
"1e2" < "3"
porównanie łańcuchowe (prawda)
a = 2; b = "2"
a == b
porównanie łańcuchowe (prawda)
a = 2; b = " +2"
a == b
porównanie łańcuchowe (fałsz)

W tym przykładzie,

$ echo 1e2 3 | awk '{ print ($1 < $2) ? "prawda" : "fałsz" }'
-| fałsz

wynikiem jest `fałsz', ponieważ $1 i $2 są łańcuchami liczbowymi, a zatem oba mają atrybut strnum, narzucający porównanie numeryczne.

Celem reguł porównywania i stosowania łańcuchów liczbowych jest próba uzyskania zachowania, które będzie "najmniej zaskakujące", w dalszym ciągu jednak "robiące poprawnie to, o co chodzi".

Porównania łańcuchów i porównania wyrażeń regularnych są całkiem odmienne. Na przykład,

x == "foo"

ma wartość jeden (jest prawdziwe) jeśli zmienna x równa się dokładnie `foo'. W przeciwieństwie do tego

x ~ /foo/

ma wartość jeden jeśli x zawiera `foo', jak np. "Oh, what a fool am I!".

Prawostronny operand operatorów `~' i `!~' może być albo wyrażeniem regularnym stałym (/.../), albo zwykłym wyrażeniem, wówczas wartość tego wyrażenia jako łańcucha jest używana jako dynamiczne wyrażenie regularne (zob. 4.1. Jak stosować wyrażenia regularne; także zob. 4.7. Stosowanie dynamicznych wyrażeń regularnych).

W ostatnich implementacjach awk, wyrażenie regularne stałe w ukośnikach jest samo również wyrażeniem (zwykłym). Wyrażenie regularne /regexp/ stanowi skrót dla tego wyrażenia porównującego:

$0 ~ /regexp/

Istnieje specjalne miejsce, w którym /foo/ nie stanowi skrótu dla `$0 ~ /foo/': wówczas, gdy jest ono prawostronnym operandem operatora `~' lub `!~'! Zob. 7.2. Używanie stałych regexp, gdzie omówiono szczegóły.

7.11. Wyrażenia logiczne

Wyrażenie logiczne jest połączeniem wyrażeń porównania lub wyrażeń dopasowania, przy użyciu operatorów logicznych "or" (`||'), "and" (`&&') i "not" (`!'), razem z nawiasami do sterowania zagnieżdżaniem. Prawdziwość wyrażenia logicznego jest obliczana jako kombinacja prawdziwości wyrażeń składowych. Wyrażenia logiczne są też nazywane wyrażeniami boole'owskimi. Oba terminy są równoważne.

Wyrażenia logiczne mogą być używane wszędzie tam, gdzie wyrażenia porównania lub dopasowania. Wykorzystuje się je w instrukcjach if, while, do i for (zob. 9. Instrukcje sterujące w akcjach). Posiadają wartości numeryczne (jeden jeśli prawda, zero jeśli fałsz), co ma znaczenie przy przypisywaniu wyniku wyrażenia logicznego zmiennej lub przy wykorzystywaniu go w działaniach arytmetycznych.

Dodatkowo, każde wyrażenie logiczne jest także poprawnym wzorcem, więc można go użyć jako wzorca do sterowania wykonaniem reguł.

Oto opis, z przykładami, trzech operatorów logicznych.

logiczne1 && logiczne2
Prawda, jeśli zarówno logiczna1 jak i logiczna2 są prawdziwe. Na przykład, poniższa instrukcja wypisuje bieżący rekord wejściowy jeśli zawiera on zarówno `2400' jak i `foo'.
if ($0 ~ /2400/ && $0 ~ /foo/) print
Podwyrażenie logiczne2 jest obliczane tylko wtedy, gdy logiczne1 jest prawdziwe. Może to mieć znaczenie gdy logiczne2 zawiera wyrażenia dające skutki uboczne: w przypadku `$0 ~ /foo/ && ($2 == bar++)', jeśli w rekordzie nie ma `foo', to zmienna bar nie jest inkrementowana.
logiczne1 || logiczne2
Prawda jeśli co najmniej jedno z wyrażeń logiczne1 lub logiczne2 jest prawdziwe. Na przykład, poniższa instrukcja wypisuje wszystkie rekordy wejścia zawierające albo `2400' albo `foo', lub oba.
if ($0 ~ /2400/ || $0 ~ /foo/) print
Podwyrażenie logiczne2 jest obliczane tylko wtedy, gdy logiczne1 jest fałszywe. Może to mieć znaczenie gdy logiczne2 zawiera wyrażenia dające skutki uboczne.
! logiczne
Prawda jeśli logiczne jest fałszywe. Na przykład, poniższy program wypisuje wszystkie rekordy pliku wejściowego `BBS-list', które nie zawierają łańcucha `foo'.
awk '{ if (! ($0 ~ /foo/)) print }' BBS-list

Operatory `&&' i `||' z powodu sposobu, w jaki działają, nazywane są operatorami skróconymi (short-circuit). Obliczenia pełnego wyrażenia są "skracane", przerywane, jeżeli wynik można określić już po obliczeniu części wyrażenia.

Instrukcję wykorzystującą `&&' lub `||' kontynuujemy po prostu stawiając po nich znak nowej linii. Nie można jednak stawiać znaku nowej linii przed tymi operatorami bez użycia kontynuacji odwróconym ukośnikiem. (zob. 2.6. Instrukcje awk a wiersze).

Faktyczną wartością wyrażenia wykorzystującego operator `!' będzie jeden lub zero, w zależności od prawdziwości wyrażenia, do którego go zastosowano.

Operator `!' przydaje się często do zmiany stanu zmiennej flagowej z fałszu na prawdę i z powrotem. Na przykład, poniższy program jest jednym ze sposobów wypisania wierszy umieszczonych między specjalnymi wierszami grupującymi:

$1 == "START"   { interested = ! interested }
interested == 1 { print }
$1 == "END"     { interested = ! interested }

Zmienna interested, jak wszystkie zmienne awk, startuje zainicjowana na zero, które jest również fałszem. Gdy zostanie spostrzeżony wiersz, którego pierwszym polem jest `START', wartość interested za pomocą `!' przełączana jest na prawdę. Następna reguła wypisuje wiersze dopóki interested jest prawdziwe. Gdy zostanie spostrzeżony wiersz, którego pierwszym polem jest `END', interested przełączane jest z powrotem na fałsz.

7.12. Wyrażenia warunkowe

Wyrażenie warunkowe jest specjalnym rodzajem wyrażenia z trzema operandami. Pozwala na wykorzystanie wartości jednego z wyrażeń do wyboru jednego z dwu pozostałych wyrażeń.

Wyrażenie warunkowe jest takie samo jak w języku C:

selektor ? wyr-jeśli-prawda : wyr-jeśli-fałsz

Mamy tu trzy podwyrażenia. Pierwsze, selektor, jest zawsze obliczane jako pierwsze. Jeżeli jest ono "prawdziwe" (nie zero i nie puste), to następnie obliczane jest wyr-jeśli-prawda a jego wartość staje się wartością całego wyrażenia. W przeciwnym razie, jako następne jest obliczane wyr-jeśli-fałsz a jego wartość staje się wartością całego wyrażenia.

Na przykład, to wyrażenie tworzy wartość bezwzględną x:

x > 0 ? x : -x

Przy każdym wyliczaniu wyrażenia warunkowego używane jest dokładnie jedno z wyr-jeśli-prawda i wyr-jeśli-fałsz; drugie jest ignorowane. Jest to istotne gdy wyrażenia mają skutki uboczne. Na przykład, to wyrażenie warunkowe bada element i albo tablicy a albo tablicy b, i inkrementuje i.

x == y ? a[i++] : b[i++]

Jest pewne, że inkrementacja i nastąpi dokładnie raz, gdyż zawsze wykonywane jest tylko jedno z dwu wyrażeń inkrementujących, drugie nie. Zob. 11. Tablice w awk, gdzie napisano więcej o tablicach.

Drobnym rozszerzeniem w gawk jest możliwość kontynuacji instrukcji wykorzystującej `?:' przez zwykłe postawienie znaku nowej linii po jednym z tych znaków. Nie można jednak stawiać znaku nowej linii przed którymś z nich bez zastosowania kontynuacji odwrotnym ukośnikiem (zob. 2.6. Instrukcje awk a wiersze). Jeżeli podano opcję `--posix' (zob. 14.1. Opcje wiersza poleceń), to rozszerzenie to jest wyłączane.

7.13. Wywołania funkcji

Funkcja jest nazwą nadaną konkretnym obliczeniom. Ponieważ mają one nazwę, można ich zażądać w dowolnym miejscu programu posługując się tą nazwą. Na przykład, funkcja sqrt oblicza pierwiastek kwadratowy z liczby.

Istnieje stały zestaw funkcji wbudowanych, co znaczy, że są one dostępne w każdym programie awk. Funkcja sqrt jest jedną z nich. Zob. 12. Funkcje wbudowane, gdzie znajduje się lista funkcji wbudowanych i ich opisy. Dodatkowo, można definiować własne funkcje, do wykorzystania w swoim programie. Zob. 13. Funkcje definiowane przez użytkownika, gdzie opisano, jak to zrobić.

Funkcje wykorzystuje się za pomocą wyrażenia wywołania funkcji, składającego się z nazwy funkcji, bezpośrednio po której następuje lista argumentów w nawiasach. Argumenty są wyrażeniami dostarczającymi surowców do obliczeń przeprowadzanych przez funkcję. Jeżeli występuje więcej niż jeden argument, to są one oddzielane przecinkami. Jeżeli brak argumentów, po nazwie funkcji piszemy same nawiasy `()'. Oto kilka przykładów:

sqrt(x^2 + y^2)        jeden argument
atan2(y, x)            dwa argumenty

rand()                 bez argumentów

Nie należy stawiać spacji między nazwą funkcji a nawiasem otwierającym! Nazwa funkcji zdefiniowanej przez użytkownika wygląda tak jak nazwa zmiennej, i spacja spowodowałaby, że całe wyrażenie wyglądałoby jak konkatenacja zmiennej z wyrażeniem wewnątrz nawiasów. Spacja przed nawiasami jest nieszkodliwa przy funkcjach wbudowanych, ale najlepiej nie nabierać nawyku wtrącania spacji, by uniknąć pomyłek przy funkcjach definiowanych przez użytkownika.

Każda funkcja oczekuje konkretnej liczby argumentów. Na przykład, funkcja sqrt musi być wywołana z pojedynczym argumentem, liczbą, z której ma wyciągnąć pierwiastek:

sqrt(argument)

Niektóre z funkcji wbudowanych pozwalają na pominięcie ostatniego argumentu. Jeśli tak postąpimy, to użyją rozsądnej wartości domyślnej. Zob. 12. Funkcje wbudowane, gdzie podano szczegóły. Jeżeli pominięto argumenty w wywołaniu funkcji definiowanej przez użytkownika, to są one traktowane jak zmienne lokalne, inicjowane łańcuchem pustym (zob. 13. Funkcje definiowane przez użytkownika).

Jak każde inne wyrażenie, wywołanie funkcji posiada wartość, obliczaną przez funkcję w oparciu o przekazane jej przez nas argumenty. W tym przykładzie, wartością `sqrt(argument)' jest pierwiastek kwadratowy argumentu. Funkcja może też mieć skutki uboczne, takie jak np. przypisanie wartości do pewnych zmiennych czy wykonanie operacji wejścia/wyjścia.

Oto polecenie do czytania liczb, po jednej w wierszu, i wypisywania pierwiastka kwadratowego każdej z nich:

$ awk '{ print "Pierwiastkiem kwadratowym z", $1, "jest", sqrt($1) }'
1
-| Pierwiastkiem kwadratowym z 1 jest 1
3
-| Pierwiastkiem kwadratowym z 3 jest 1.73205
5
-| Pierwiastkiem kwadratowym z 5 jest 2.23607
Control-d

7.14. Priorytet operatorów (Jak łączą się różne operatory)

Priorytet operatorów określa sposób, w jaki są grupowane operatory gdy pojawiają się tuż obok siebie w jednym wyrażeniu. Na przykład, `*' ma wyższy priorytet niż `+'; zatem, `a + b * c' oznacza przemnożenie b i c, a następnie dodanie a do iloczynu (tj. `a + (b * c)').

Nad priorytetem operatorów nadrzędna jest kolejność narzucona zastosowanymi nawiasami. Można myśleć o regułach priorytetów tak, jakby stwierdzały one, gdzie przyjęte zostanie położenie nawiasów, jeżeli sami ich nie postawimy. W rzeczywistości, rozsądnie jest przy nietypowej kombinacji operatorów zawsze stosować nawiasy, gdyż inni czytający program mogą nie pamiętać, jaki jest priorytet w tym przypadku. Możemy o tym również zapomnieć sami lub też popełnić pomyłkę. Jawne nawiasy pomogą uniknąć podobnych błędów.

Kiedy operatory o równym priorytecie są użyte razem, to jako pierwszy grupuje operator występujący po lewej, za wyjątkiem przypisania, operatorów warunkowych i potęgowania, gdzie grupowanie odbywa się w odwrotnej kolejności. Zatem, `a - b + c' grupuje jak `(a - b) + c', a `a = b = c' grupuje jak `a = (b = c)'.

Priorytet jednoargumentowych operatorów przedrostkowych nie ma znaczenia póki zaangażowane są tylko operatory jednoargumentowe, gdyż jest tylko jeden sposób ich interpretacji -- poczynając od najbardziej wewnętrznego. Zatem, `$++i' oznacza `$(++i)' a `++$x' znaczy `++($x)'. Jeżeli jednak po operandzie występuje inny operator, to priorytet operatorów jednoargumentowych może mieć znaczenie. Zatem, `$x^2' oznacza `($x)^2', ale `-x^2' oznacza `-(x^2)', gdyż `-' ma niższy priorytet niż `^', podczas gdy `$' ma wyższy.

Oto tabela operatorów występujących w awk, w kolejności od najwyższego priorytetu do najniższego:

(...)
Grupowanie.
$
Pole.
++ --
Inkrementacja, dekrementacja.
^ **
Potęgowanie. Operatory te grupują od prawej do lewej. (POSIX nie wymienia operatora `**'.)
+ - !
Jednoargumentowy plus, minus, negacja logiczna ("not").
* / %
Mnożenie, dzielenie, reszta z dzielenia (modulo).
+ -
Dodawanie, odejmowanie.
Konkatenacja
Do wskazania konkatenacji nie jest wykorzystywany żaden specjalny znacznik. Operandy są po prostu zapisywane jeden obok drugiego.
< <= == !=
> >= >> |
Relacje i przekierowania. Operatory relacji i przekierowania mają ten sam poziom priorytetu. Znaki takie jak `>' służą zarówno do zapisu relacji jak i przekierowań. Oba znaczenia są odróżniane po kontekście. Zwróć uwagę, że operatory przekierowania wejścia/wyjścia w instrukcjach print i printf należą do poziomu instrukcji, nie do wyrażeń. Przekierowanie nie tworzy wyrażenia, które mogłoby być operandem innego operatora. Wskutek tego, nie ma sensu stosowanie operatora przekierowania w pobliżu innego operatora o niższym priorytecie, bez użycia nawiasów. Takie kombinacje, na przykład `print foo > a ? b : c', powodują błędy składniowe. Poprawnym sposobem zapisania tej instrukcji jest `print foo > (a ? b : c)'.
~ !~
Dopasowanie, nie-dopasowanie.
in
Przynależność do tablicy.
&&
Koniunkcja logiczna ("and").
||
Alternatywa logiczna ("or").
?:
Operator warunkowy. Grupuje od prawej do lewej.
= += -= *=
/= %= ^= **=
Przypisanie. Te operatory grupują od prawej do lewej. (POSIX nie wymienia operatora `**'.)

 


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.