<<< оглавление >>>

руководство пользователя для gnu awk

arnold d. robbins
перевод балуева а. н.

13. функции, определенные пользователем

сложные awk-программы часто могут быть упрощены определением ваших собственных функций. пользовательские функции могут вызываться точно так же как и встроенные (см. раздел 7.13 [вызовы функций], стр. 93), но определяя их вы должны сами сообщить awk, что они должны делать.

13.1 синтаксис определения функции

определения функций могут располагаться где угодно между правилами программы awk. таким образом, общая форма программы awk расширяется и включает последовательности правил и определений функций пользователя. нет необходимости помещать определения функций перед обращениями к ним, так как awk читает всю программу перед началом ее выполнения.

определение функции с именем  name выглядит так:

function name(parameter-list) {
body-of-function }

name есть имя определяемой функции. правильное имя функции подобно правильному имени переменной: последовательность букв, цифр и подчеркиваний, начинающаяся не с цифры. в пределах одной awk-программы каждое имя может быть использовано как переменная, массив или функция.

parameter-list есть список аргументов функции и локальных имен переменных, разделенных запятыми. когда функция вызывается, имена аргументов используются для помещения значений аргументов, указанных в вызове. локальные переменные инициализируются пустыми цепочками. функция не может иметь двух параметров с одинаковыми именами.

тело функции состоит из операторов awk. это наиболее важная часть определения, так как она говорит, что функция должна делать. имена аргументов существуют, чтобы дать телу возможность различать аргументы; локальные переменные предоставляют телу места для временных значений. синтаксически имена аргументов неотличимы от имен локальных переменных; вместо этого, количество аргументов, указанных в вызове функции, определяет, как много имеется аргументных переменных. так, если даны три значения аргументов, первые три имени в списке параметров есть аргументы, а остальные являются локальными переменными. если количество аргументов не одно и тоже во всех вызовах функции, некоторые имена в списке параметров могут быть аргументами в одних случаях и локальными переменными в других. иначе говоря, можно считать, что опущенные аргументы по умолчанию получают значение пустой цепочки.

обычно, когда вы пишите функцию, вы знаете, сколько имен вы намерены использовать для аргументов и сколько для локальных переменных. при программировании обычно помещают несколько дополнительных пробелов между аргументами и локальными переменными для указания того, как функция будет использоваться. во время выполнения тела функции аргументы и значения локальных переменных прячут, или затеняют, те же самые имена, использованные в остальной программе. затененные переменные недоступны из определения функции, потому что невозможно обратиться к ним, пока их имена заняты локальными переменными. все другие переменные, используемые в awk-программе, доступны для ссылок или присваиваний из тела функции. аргументы и локальные переменные играют особую роль только на время выполнения тела функции. как только оно окончено, можно опять обращаться к переменным, которые были затенены при выполнении тела.

тело функции может содержать выражения, которые вызывают функции. они могут даже вызывать определяемую функцию, или непосредственно или через вызов другой функции. мы говорим в таких случаях, что функция рекурсивна.

во многих реализациях awk, включая gawk, ключевое слово function может быть сокращено до func. однако posix указывает только слово function. фактически это имеет некоторые практические следствия. если gawk находится в posix-совместимом режиме (см. раздел 14.1 [параметры командной строки], стр. 161), то следующий оператор не определяет функцию:

func foo() { a = sqrt($1) ; print a }

он определяет правило, которое для каждой записи сцепляет значение переменной `func' с возвращенным значением функции `foo'. если цепочка-результат не пуста, действие выполняется. и это, конечно, будет не то, что задумано. (awk принимает такой ввод как синтаксически правильный, поскольку функции могут использоваться раньше их определения в программах.) для обеспечения переносимости программ употребляйте всегда в определениях ключевое слово function.

13.2 примеры определения функций

вот определение пользовательской функции с именем myprint, которая берет число и печатает его в указанном формате.

function myprint(num) {
printf "%6.3g\n", num }

приведем для иллюстрации  awk-правило, которое использует эту функцию:

$3 > 0 { myprint($3) }

эта программа печатает в специальном формате все третьи поля, которые содержат положительное число. поэтому, если дано:

1.2 3.4 5.6 7.8 9.10 11.12 -13.14 15.16 17.18 19.20 21.22 23.24

то эта программа, используя нашу функцию для форматирования результата, напечатает:

5.6 21.2
следующая функция вычеркивает все элементы массива.

function delarray(a, i) {
for (i in a)
delete a[i] }

при работе с массивами часто нужно вычеркивать все элементы некоторого массива и начинать с начала с новым списком элементов (см. раздел 11.6 [оператор delete], стр. 128). вместо повторения такого цикла всюду в программе, everywhere где вы хотите очистить массив, можно просто вызывать delarray.

приведем пример рекурсивной функции. она берет цепочку в качестве входного параметра и возвращает цепочку в обратном порядке.

function rev(str, start) 
{
if (start == 0)
return ""
return (substr(str, start, 1) rev(str, start - 1)) 
}

если эта функция находится в файле с именем `rev.awk', мы можем проверить ее следующим образом:

$ echo "don't panic!"| 
> gawk --source '{ print rev($0, length($0)) }' -f rev.awk
-| !cinap t'nod

вот пример, где используется встроенная функция strftime. (см. раздел 12.5 [функции для действий с отметками времени], стр. 148, для подробной информации о strftime.) функция ctime в си получает timestamp и возвращает ее в хорошо известной форме. здесь приведена ее awk-версия:

# ctime.awk # # awk-версия функции  ctime(3) в си
function ctime(ts, format) 
{
format = "%a %b %d %h:%m:%s %z %y" if (ts == 0)
ts = systime() # по умолчанию используется текущее время
return strftime(format, ts) 
}

13.3 вызов функций, определенных пользователем

вызов функции подразумевает запуск ее тела для выполнения необходимых действий. вызов функции есть выражение, и его значение есть значение, возвращаемое функцией.

вызов функции состоит из имени функции с последующими аргументами в скобках. то, что вы пишите в вызове в качестве аргументов, также есть выражения awk; каждый раз, когда выполняется вызов, эти выражения вычисляются и их значения становятся фактическими аргументами. например, вот вызов foo с тремя аргументами (первый есть конкатенация цепочек):

foo(x y, "lose", 4 * z)

внимание: символы whitespace (пробелы и табуляции) не допускаются между именем функции и открывающей скобкой списка аргументов. если вы по ошибке напишете здесь whitespace, то awk может подумать, что вы хотите соединить переменную с выражением в скобках. вместе с тем awk замечает, что используется имя функции, а не переменной, и выдает сообщение об ошибке.

когда вызывается функция, она получает копии значений ее аргументов. это называется вызов по значению. вызывающая сторона может использовать переменную как выражение для аргумента, но вызываемая функция этого не знает: она знает только значение аргумента. например, если вы напишите такой код:

foo = "bar" z = myfunc(foo)

вы не должны думать об аргументе для myfunc как о "переменной foo." вместо этого вы должны думать об аргументе как о строковом значении "bar". если функция myfunc изменяет значения своих локальных переменных, это не влияет ни на какие другие переменные. так, если myfunc делает так:

function myfunc(str) 
{
print str str = "zzz" print str 
}

для изменения своего первого аргумента --- переменной str, это не изменит значения foo в месте вызова. роль foo при вызове myfunc кончается, когда вычисляется ее значение "bar". если str также существует вне myfunc, тело функции не может изменить это внешнее значение, потому что оно затенено во время выполнения тела myfunc и не может быть увидено или изменено оттуда.

вместе с тем, когда массивы служат параметрами функций, они не копируются. наоборот, они доступны для прямых манипуляций из тела функции. это обычно называется вызов по имени (или по ссылке). изменения, сделанные в параметре массиве внутри тела функции, видимы вне этой функции. это может быть очень опасно, если не следить за тем, что вы делаете.

например:

function changeit(array, ind, nvalue) 
{
array[ind] = nvalue 
}
begin {
a[1] = 1; a[2] = 2; a[3] = 3 changeit(a, 2, "two")
printf "a[1] = %s, a[2] = %s, a[3] = %s\n",
a[1], a[2], a[3] }

эта программа печатает `a[1] = 1, a[2] = two, a[3] = 3', потому что changeit присваивает "two" второму элементу массива a.

некоторые реализации awk позволяют вызывать функции, которые не были определены, и только сообщают об этом во время исполнения, когда программа фактически пытается вызвать функцию.

например:

begin {
if (0)
foo() else
bar() "" function bar() - ... } # заметим, что  `foo' не определена

так как оператор `if' никогда не будет `true', неопределенность foo не создает проблем. хотя обычно это проблема, если программа вызывает неопределенную функцию.

если было указано `--lint' (см. раздел 14.1 [параметры командной строки], стр. 161), gawk сообщит о вызове неопределенных функций.

некоторые реализации awk генерируют динамические ошибки, если вы используете оператор next (см, раздел 9.7 [оператор next], стр. 111) внутри функции пользователя. gawk не имеет этой проблемы.

13.4 оператор return

тело функции пользователя может содержать оператор return. этот оператор возвращает управление продолжению awk-программы. его можно также использовать для возврата значения функции для использования в остальной части программы. это выглядит так: return [expression]. часть expression не обязательна. если она опущена, возвращаемое значение не определено и, следовательно, непредсказуемо. оператор return без expression подразумевается в конце каждого определения функции. так что если управление достигает конца тела функции, то функция возвращает непредсказуемое значение. awk не предупреждает об использовании возвращенного значения такой функции.

иногда нужно написать функцию для выполнения определенных действий, а не для получения возвращаемого значения. такая функция соответствует пустой функции в си или процедуре в паскале. так, может быть целесообразным не возвращать никакого значения; нужно просто держать в уме, что если вы используете значение такой функции, вы идете на риск. вот пример пользовательской функции, которая возвращает значение наибольшего числа среди элементов массива:

function maxelt(vec, i, ret) 
{
for (i in vec) 
    {
     if (ret == "" || vec[i] > ret)
           ret = vec[i] 
     } 
return ret 
}

вы вызываете maxelt с одним аргументом, который есть имя массива. локальные переменные i и ret не предназначены быть аргументами; если бы не было ничего, что препятствовало бы вам передать два или три аргумента функции maxelt, результаты могли бы быть странными. дополнительный пробел перед i в списке параметров указывает, что i и ret не предполагаются быть аргументами. это есть соглашение, которому вы должны следовать при определении функций.

вот программа, которая использует нашу функцию maxelt. она заполняет массив, вызывает maxelt и затем выдает максимальное число в этом массиве:

awk ' function maxelt(vec, i, ret) 
{
for (i in vec) 
    {
     if (ret == "" || vec[i] > ret)
           ret = vec[i] 
     } 
return ret 
}
# загрузка всех полей каждой записи в  nums. 
{
for(i = 1; i <= nf; i++)
nums[nr, i] = $i 
}
end {
print maxelt(nums) 
}'

если задан следующий ввод:

1 5 23 8 16 44 3 5 2 8 26 256 291 1396 2962 100 -6 467 998 1101 99385 11 0 225

наша программа должна сообщить нам, что 99385 есть наибольшее число в массиве.


<<< оглавление >>>