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.
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.
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.
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).
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.
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.
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.
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).
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.
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
+ x
x ^ y
x ** y
x * y
x / y
awk są liczbami
zmiennoprzecinkowymi,
wynik nie jest zaokrąglany do całkowitej: `3 / 4' ma wartość 0.75.
x % y
b * int(a / b) + (a % b) == aPewnym, 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 = -1W innych implementacjach
awk, uzyskiwany znak reszty z dzielenia może
być zależny od architektury komputera.
x + y
x - y
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.
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 `=').
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
lwartość -= dekrement
lwartość *= współczynnik
lwartość /= dzielnik
lwartość %= współczynnik
lwartość ^= potęga
lwartość **= potęga
W celu zachowania maksymalnej przenośności nie należy stosować operatora `**='.
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ść
lwartość++
--lwartość
lwartość--
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.).
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ł.
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.
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
x <= y
x > y
x >= y
x == y
x != y
x ~ y
x !~ y
indeks in tabl
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
"abc" >= "xyz"
1.5 != " +2"
"1e2" < "3"
a = 2; b = "2"
a == b
a = 2; b = " +2"
a == b
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.
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
if ($0 ~ /2400/ && $0 ~ /foo/) printPodwyraż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
if ($0 ~ /2400/ || $0 ~ /foo/) printPodwyraż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
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.
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.
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
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:
(...)
$
++ --
^ **
+ - !
* / %
+ -
Konkatenacja
< <= == !=
> >= >> |
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)'.
~ !~
in
&&
||
?:
= += -= *=
/= %= ^= **=
Przejdź do pierwszej, poprzedniej, następnej, ostatniej sekcji, spisu treści.