Руководство пользователя для GNU Awk

Arnold D. Robbins
перевод Балуева А. Н.

15. Библиотека функций awk

Оглавление

15.10 Обработка параметров командной строки

В начало страницы

Большинство средств на совместимых с POSIX системах принимают из командной строки параметры или "переключатели", которые могут использоваться для изменения поведения соответствующих программ. awk есть пример такой программы. (см. раздел 14.1 [Параметры командной строки], стр. 161). Часто параметры имеют аргументы, т.е. данные, в которых нуждается программа для правильного выполнения требований параметров. Например, awk-параметр `-F' требует цепочку для употребления в качестве разделителя полей. Первое вхождение в командной строке или `--' или цепочки, не начинающейся с `-' , означает конец параметров.

Большинство систем Unix имеют функцию языка Cи с именем getopt для обработки аргументов командной строки. Программист выдает цепочку, описывающую однобуквенный параметр. Если параметр требует какой-нибудь аргумент, за ним следует двоеточие. getopt также передаются количество и значения аргументов командной строки и она вызывается в цикле. getopt обрабатывает аргументы командной строки для букв параметров. На каждом обороте цикла она возвращает один символ, представляющий следующую букву параметра, которую обнаружила, или `?', если обнаружила неправильный параметр. Когда она возвращает \Gamma 1, это значит, что в командной строке больше нет параметров.

При использовании getopt параметры, у которых нет аргументов, могут быть сгруппированы вместе. Для параметров, которые могут иметь аргументы, требуется присутствие аргументов. Аргументы могут непосредственно следовать за буквой параметра или могут быть отдельными аргументами командной строки.

Пусть некая гипотетическая программа использует три параметра командной
строки, `-a', `-b' и `-c', и `-b' требует один аргумент. Тогда правильными
будут все следующие способа запуска этой программы:
prog -a -b foo -c data1 data2 data3
prog -ac -bfoo -- data1 data2 data3
prog -acbfoo data1 data2 data3

Отметим, что когда аргумент сгруппирован со своим параметром, остальные аргументы командной строки рассматриваются как аргументы этого параметра. В предыдущем примере `-acbfoo' указывает, что приведены все параметры `-a', `-b', и `-c', и что `foo' есть аргумент параметра `-b'.

getopt

дает четыре внешних переменных, которые программист может использовать:

optind

Индекс в массиве значений аргументов argv, показывающий где найти первый, не являющийся параметром аргумент командной строки.

optarg

Строковое значение аргумента параметра.

opterr

Обычно getopt печатает сообщение об ошибке, когда находит неверный параметр. Присваивание нуля переменной opterr выключает это свойство. (Прикладная программа может захотеть напечатать свое собственное сообщение.)

optopt

Буква, представляющая параметр командной строки. Хотя это обычно не документировано, большинство версий обладают этой переменной.

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

int main(int argc, char *argv[]) -

... /* print our own message */ opterr = 0;

while ((c = getopt(argc, argv, "v:f:F:W:")) != -1) -
switch (c) - case 'f': /* file */
... break; case 'F': /* field separator */
... break; case 'v': /* variable assignment */
... break; case 'W': /* extension */
... break; case '?': default:
usage(); break; "" "" ... ""

Попутно gawk фактически использует функцию GNU getopt.long для обработки обычных и длинных, в стиле GNU, параметров (см. раздел 14.1 [Параметры командной строки], стр. 161).

Абстракция, которую обеспечивает getopt, очень полезна, она также может очень помочь в awk-программах. Приведем awk-версию для getopt. Эта функция подчеркивает одну из главных слабостей awk--- трудность манипуляций с отдельными символами. Для получения отдельных символов необходимо повторять вызовы substr (см. раздел 12.3 [Встроенные функции для манипуляций с цепочками], стр. 137). Дискуссии об этом заняли довольно много времени.


# getopt --- представляет в awk функцию getopt(3) из библиотеки Си
# # arnold@gnu.org # Public domain # # Initial version: March, 1991
# Revised: May, 1993
# External variables:
# Optind -- индекс в ARGV для первого аргумента не параметра
# Optarg -- цепочка значения аргумента текущего параметра
# Opterr -- если не ноль, то печатать нашу собственную диагностику
# Optopt -- текущая буква параметра
# Возвращает # -1 в конце параметров # ? для неопознанных параметров
# !c? символ, представляющий текущий параметр
# Private Data # .opti индекс в много флажковых параметрах, например, в  -abc

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

function getopt(argc, argv, options, optl, thisopt, i) -
optl = length(options) if (optl == 0) # при отсутствии параметров
return -1
if (argv[Optind] == "--") - # все проделано

Optind++ .opti = 0 return -1 "" else
if (argv[Optind] !~ /^-[^: "t"n"f"r"v"b]/) -
.opti = 0 return -1 ""

Функция сначала проверяет, что она действительно вызвана с цепочкой параметров (аргумент options). Если options имеет длину 0, getopt сразу же возвращает \Gamma 1.

Идет проверка на конец options. Цепочка `--' означает конец параметров в командной строке, так же как любой аргумент командной строки, не начинающийся с `-'. Optind используется для продвижения по массиву аргументов командной строки; он сохраняет свое значение во всех обращениях к getopt, поскольку является глобальной переменной.

Используемое регулярное выражение /^-[^: "t"n"f"r"v"b]/ , по-видимому, несколько избыточно; оно ищет `-', за которыми следует что-то, отличное от whitespace или двоеточия.

Если текущий аргумент командной строки не соответствует этому образцу, он не параметр и прекращает обработку параметров.

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

Переменная opti следит за позицией в текущем аргументе командной строки (argv[Optind]). В случае, когда несколько параметров собраны вместе с одним `-' (например, `-abx'), необходимо передать их пользователя по одному за раз. Если .opti равно нулю, оно получает значение два, индекс следующего символа в цепочке для просмотра (мы пропускаем `-', стоящий в позиции один). Переменная thisopt содержит символ, полученный из цепочки. Он сохраняется в Optopt для использования в главной программе.

Если thisopt отсутствует в цепочке параметров, то это ошибочный параметр. Если Opterr не ноль, getopt печатает сообщение об ошибке в стандартном выходе для сообщений об ошибках, подобно сообщениям в Си-версии getopt.

Поскольку параметр неверен, его необходимо пропустить и двигаться к следующему символу параметра. Если .opti больше или равна длине текущего аргумента командной строки, то необходимо перейти к следующему, и Optind увеличивается, а .opti сбрасывается в 0. В противном случае Optind остается без изменения, а увеличивается только .opti

В любом случае, когда параметр ошибочен, getopt возвращает `?'. Главная программа может исследовать Optopt, если хочет узнать, какой буквой оказался неверный параметр.

if (substr(options, i + 1, 1) == ":") -
# выдает аргумент параметра
if (length(substr(argv[Optind], .opti + 1)) ? 0)
Optarg = substr(argv[Optind], .opti + 1) else
Optarg = argv[++Optind] .opti = 0 "" else
Optarg = ""

Если параметр требует аргумент, за буквой параметра в цепочке параметров следует двоеточие. Если в текущем аргументе командой строки (argv[Optind]) остаются еще символы, то остаток цепочки присваивается переменной Optarg. В противном случае используется следующий аргумент командной строки (`-xFOO' или `-x FOO'). В любом случае .opti сбрасывается в 0, так как больше не остается символов для исследования в текущем аргументе командной строки.

if (.opti == 0 ---- .opti ?= length(argv[Optind])) -
Optind++ .opti = 0 "" else
.opti++ return thisopt ""

Наконец, если .opti или ноль или больше чем длина текущего аргумента командной строки, то это значит, что этот элемент в argv полностью исследован, поэтому Optind увеличивается для указания на следующий в argv. Если ни одно условие не имеет значения true, то продвигается только .opti, так что следующая буква-параметр может обрабатываться следующим вызовом getopt.

BEGIN -
Opterr = 1 # default is to diagnose Optind = 1 # skip ARGV[0]
# test program
if (.getopt.test) -
while ((.go.c = getopt(ARGC, ARGV, "ab:cd")) != -1)
printf("c = !%c?, optarg = !%s?"n",
.go.c, Optarg) printf("non-option arguments:"n")
for (; Optind ! ARGC; Optind++)
printf(""tARGV[%d] = !%s?"n",
Optind, ARGV[Optind]) "" ""

Правило BEGIN инициализирует единицей обе Opterr и Optind. Opterr устанавливается в единицу, поскольку по умолчанию поведение getopt есть печать диагностических сообщений при обнаружении неверных параметров. Optind устанавливается в единицу, поскольку нет причин смотреть на имя программы, которое находится в ARGV[0]. Остальная часть BEGIN есть просто тест программы.

Приведем два примера пропуска тестовых программ.

$ awk -f getopt.awk -v .getopt.test=1 -- -a -cbARG bax -x
a c = !a?, optarg = !? a c = !c?, optarg = !? a c = !b?, optarg = !ARG?
a non-option arguments: a ARGV[3] = !bax? a ARGV[4] = !-x?
$ awk -f getopt.awk -v .getopt.test=1 -- -a -x -- xyz abc
a c = !a?, optarg = !?
error x -- invalid option
a c = !??, optarg = !?
a non-option arguments:
a ARGV[4] = !xyz? a ARGV[5] = !abc?

Первое `--' заканчивает аргументы awk, так что это не попытка рассматривать `-a' и следующие как ее собственные параметры. Несколько примеров программ, представленных в Главе 16 [Практические awk-программы], стр. 205, используют getopt для обработки своих аргументов.

15.11 Чтение пользовательской базы данных

В начало страницы

Специальный файл `/dev/user' (см. раздел 6.7 [Специальные имена файлов в gawk], стр. 72) обеспечивает доступ к текущему пользовательскому реальному и эффективному пользовательскому и групповому идентификационным номерам и, если есть, к дополнительному номеру пользовательского группового множества. Однако, поскольку это номера, они не содержат очень полезной для обычного пользователя информации. Хотелось бы найти некоторый путь к информации пользователя, связанной с пользователем и с номерами групп. Этот раздел предлагает комплект функций для поиска информации в пользовательских базах данных. См. раздел 15.12 [Чтение групповой базы данных], стр. 197, о подобном комплекте, который выдает информацию из групповых баз данных. Стандарт POSIX не определяет файл, где пользователь держит информацию. Вместо этого он обеспечивает заголовочный файл !pwd.h? и несколько подпрограмм на языке Си для получения пользовательской информации. Первичная функция есть getpwent ( сокращение от "get password entry", т.е. узнать пароль). Пароль получают из оригинального файла пользовательской базы данных, /etc/passwd', который содержит информацию пользователя вместе с зашифрованными паролями (отсюда и имя файла).

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

Чтобы быть в состоянии произвести читаемую, полную версию пользовательской базы данных, необходимо написать маленькую Cи-программу, которая вызывает getpwent. getpwent определена для возвращения указателя к структуре passwd. При каждом вызове она возвращает следующий вход в базу данных. Если входов больше нет, она возвращает NULL, нулевой указатель. Когда это происходит, Cи-программа должна вызывать endpwent для закрытия базы данных. Приведем pwcat, Си-программу, которая "закошачивает" (cats) базу данных с паролем.

/*

* pwcat.c * * Генерирует печатную версию  базы данных с паролем
* * Arnold Robbins * arnold@gnu.org * May 1993 * Public Domain */
#include !stdio.h? #include !pwd.h?
int main(argc, argv) int argc; char **argv; -
struct passwd *p;
while ((p = getpwent()) != NULL)
printf("%s:%s:%d:%d:%s:%s:%s"n",
p-?pw.name, p-?pw.passwd, p-?pw.uid, p-?pw.gid, p-?pw.gecos, p-?pw.dir,
p-?pw.shell);
endpwent(); exit(0); ""

Если вы не понимаете Си, не беспокойтесь. Выход от pwcat есть пользовательская база данных в традиционном формате `/etc/passwd' разделенных двоеточиями полей. Поля такие:

Login name

Пользовательское login name.

Зашифрованный пароль Пользовательский зашифрованный пароль . Он может быть недоступен на некоторых системах.

User-ID

Пользовательский идентификационный номер.

Group-ID

Числовой идентифкатор группы пользователей.

Full name

Полное имя пользователя ,возможно, и другая информация, связанная с ним.

начальный каталог

Начальный или входной каталог пользователя ($HOME по терминологии системных программистов).

Login shell

Программа, которая выполняется при входе пользователя в систему. Обычно это оболочка, такая как Bash (оболочка Gnu Bourne-Again).

Приведем несколько строк, представляющих выход  pwcat.

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

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

# passwd.awk --- доступ к информации в файле password
# Arnold Robbins, arnold@gnu.org, Public Domain # May 1993
BEGIN -
# приспособьте это для своей системы
.pw.awklib = "/usr/local/libexec/awk/" ""
function .pw.init( oldfs, oldrs, olddol0, pwcat) -
if (.pw.inited)
return oldfs = FS oldrs = RS olddol0 = $0 FS = ":" RS = ""n"
pwcat = .pw.awklib "pwcat" while ((pwcat -- getline) ? 0) -
.pw.byname[$1] = $0 .pw.byuid[$3] = $0 .pw.bycount[++.pw.total] = $0 ""
close(pwcat) .pw.count = 0 .pw.inited = 1 FS = oldfs RS = oldrs
$0 = olddol0 ""

Правило BEGIN устанавливает частную переменную на каталог, где хранится pwcat. Поскольку она используется для помощи в извлечении библиотечной подпрограммы awk, мы решили положить ее в `/usr/local/libexec/awk'. Вы можете держать ее в другом каталоге в вашей системе.

Функция .pw.init хранит три копии информации о пользователях в трех ассоциативных массивах. Массивы индексируются по имени пользователей (.pw.byname), по идентификационному номеру пользователей (.pw.byuid), и по порядку расположения (.pw.bycount).

Переменная .pw.inited используется для эффективности; .pw.init нужно вызывать только один раз.

Так как эта функция использует getline для чтения информации из pwcat, она сначала запоминает значения FS, RS и $0. Это делать необходимо, поскольку эти функции могут вызываться из любого места программы пользователя, и пользователь может иметь свои собственные значения для FS и RS.

Главная часть функции использует цикл для чтения строк базы данных, разделения их на поля и затем при необходимости запоминает строки в каждом массиве. Когда цикл закончен, .pw.init завершает работу, закрывая конвейер, устанавливая .pw.inited в единицу и восстанавливая FS, RS и $0. Использование .pw.count будет пояснено ниже.

function getpwnam(name) -
.pw.init() if (name in .pw.byname)
return .pw.byname[name] return "" ""

Функция getpwnam получает имя пользователя в качестве строкового аргумента. Если этот пользователь находится в базе, она возвращает соответствующую строку. Иначе ответом служит пустая цепочка.

function getpwuid(uid) -
.pw.init() if (uid in .pw.byuid)
return .pw.byuid[uid] return "" ""

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

function getpwent() -
.pw.init() if (.pw.count ! .pw.total)
return .pw.bycount[++.pw.count] return "" ""

Функция getpwent просто просматривает базу данных, по одному входу за раз. Она использует .pw.count для слежения за своей текущей позицией в массиве.

function endpwent() -
.pw.count = 0 ""

Функция endpwent сбрасывает .pw.count на ноль, так что последующие обращения к getpwent будут опять начинать с начала.

При проектировании этого комплекта функций предполагалось, что каждая подпрограмма вызывает .pw.init для инициализации массивов базы данных Служебный отдельный процесс для генерирования пользовательской базы данных и I/O для просмотра ее только и будут обязательны, если главная программа пользователя фактически вызывает одну из этих функций. Если этот библиотечный файл загружен вместе с пользовательской программой, но ни одна из подпрограмм ни разу не вызывается, не будет никакого дополнительного расхода машинного времени.

(Альтернативой может быть перенос тела .pw.init в правило BEGIN, которое будет всегда выполнять pwcat. Это упрощает код, но запускает дополнительный процесс, который может никогда не потребоваться.) В свою очередь, вызов pw.init не очень дорог, поскольку переменная .pw.inited предохраняет программу от чтения данных более одного раза. Если вы заинтересованы в удалении каждого лишнего цикла из вашей awk-программы, то проверка .pw.inited может быть выделена из .pw.init и дублирована во всех других функциях. Практически, это не необходимо, поскольку большинство awk-программ ориентированы на ввод и вывод и упомянутые действия только запутают ее код.

Программа id в разделе 16.1.3 [Печать информации о пользователях], стр.215, использует эти функции.

15.12 Чтение групповых баз данных

В начало страницы

Большинство рассуждений, представленных в разделе 15.11 [Чтение пользовательской базы данных], стр. 192, в такой же мере приложимы и к групповым базам данных. Хотя традиционно использовался известный файл, `/etc/group', в хорошо известном формате, стандарт POSIX имеет только группу подпрограмм из библиотеки Си (!grp.h? и getgrent) для доступа к информации. Даже если такой файл существует, он вероятно не содержит полной информации. Поэтому, как и с пользовательской базой данных, необходимо иметь небольшую Си-программу, которая генерирует групповую базу как свой выход.

Приведем grcat, программу языка Си, которая "окошачивает" ("cats") групповую базу данных.

/*
* grcat.c * * Generate a printable version of the group database
* * Arnold Robbins, arnold@gnu.org * May 1993 * Public Domain */
#include !stdio.h? #include !grp.h?
int main(argc, argv) int argc; char **argv; -
struct group *g; int i;
while ((g = getgrent()) != NULL) -
printf("%s:%s:%d:", g-?gr.name, g-?gr.passwd,
g-?gr.gid); for (i = 0; g-?gr.mem[i] != NULL; i++) -
printf("%s", g-?gr.mem[i]); if (g-?gr.mem[i+1] != NULL)
putchar(','); "" putchar('"n'); "" endgrent(); exit(0); ""

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

Group Name

Имя группы.

Group Password

Зашифрованный групповой пароль. Это поле практически никогда не используется. Оно обычно пусто или содержит `*'.

Group ID Number

Числовой идентификатор группы. Номер должен быть уникальны в файле.

Group Member List

Разделенный запятыми список имен пользователей. Эти пользователи являются членами группы. Большинство систем Unix разрешают пользователям быть членами нескольких групп одновременно. Если это возможно и в вашей системе, то чтение `/dev/user' возвратит эти групповые номера в $5 через $NF. (Заметим что `/dev/user' есть расширение gawk; см. раздел 6.7 [Специальные имена файлов в gawk], стр. 72.)

Ниже показано, что может привести выполнение  grcat:

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

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

# group.awk --- функции для действий с групповым файлом
# Arnold Robbins, arnold@gnu.org, Public Domain # May 1993
BEGIN " -
# измените в соответствии с вашей системой
.gr.awklib = "/usr/local/libexec/awk/" ""
function .gr.init( oldfs, oldrs, olddol0, grcat, n, a, i) -
if (.gr.inited)
return
oldfs = FS oldrs = RS olddol0 = $0 FS = ":" RS = ""n"
grcat = .gr.awklib "grcat" while ((grcat -- getline) ? 0) -
if ($1 in .gr.byname)
.gr.byname[$1] = .gr.byname[$1] "," $4 else
.gr.byname[$1] = $0 if ($3 in .gr.bygid)
.gr.bygid[$3] = .gr.bygid[$3] "," $4 else
.gr.bygid[$3] = $0
n = split($4, a, "[ "t]*,[ "t]*") for (i = 1; i != n; i++)
if (a[i] in .gr.groupsbyuser)
.gr.groupsbyuser[a[i]] = "
.gr.groupsbyuser[a[i]] " " $1 else
.gr.groupsbyuser[a[i]] = $1
.gr.bycount[++.gr.count] = $0 ""
close(grcat) .gr.count = 0 .gr.inited++ FS = oldfs RS = oldrs $0 = olddol0 ""

Правило BEGIN устанавливает частную переменную на каталог, где хранится grcat. Поскольку она используется для помощи в извлечении подпрограммы из awk-библиотеки, мы решили положить ее в`/usr/local/libexec/awk'. Вы можете разместить ее в другом каталоге в вашей системе.

Эти подпрограммы следуют той же самой общей форме, что и подпрограммы пользовательской базы данных (см. раздел 15.11 [Чтение пользовательской базы данных], стр. 192). Переменная .gr.inited используется для обеспечения сканирования базы не более одного раза. Функция .gr.init сначала запоминает FS, RS и $0, затем устанавливает в FS и RS правильные значения для сканирования групповой информации. Эта информация записывается в несколько ассоциативных массивов. Массивы индексируются групповыми именами (.gr.byname), групповыми идентификационными номерами (.gr.bygid) и позициями в базе данных (.gr.bycount). Имеется дополнительный массив, индексированный именами пользователей (.gr.groupsbyuser), представляющий список разделенных пробелами групп, к которым принадлежит каждый пользователь. В отличие от пользовательской базы данных, можно иметь кратные записи в базе данных для той же самой группы. Это обычно, когда группа имеет большое количество членов. Подобная пара входов может выглядеть так:

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

По этой причине .gr.init смотрит, не встречалось ли раньше некоторое групповое имя или групповой идентификатор. Если да, пользовательские имена просто присоединяются к прежнему списку пользователей. (Фактически имеется небольшая проблема с представленным выше кодом. Предположим, что в первое время никаких имен не было. Этот код добавляет имена с ведущей запятой. Он также не проверяет, что имеется $4.) В конце .gr.init закрывает конвейер к grcat, восстанавливает FS, RS и $0, инициализирует .gr.count нулем (это нужно позднее) и делает .gr.inited не нулем.

function getgrnam(group) -
.gr.init() if (group in .gr.byname)
return .gr.byname[group] return "" ""

Функция getgrnam получает в качестве аргумента групповое имя, и если эта группа имеется, то она и возвращается. В противном случае getgrnam возвращает пустую цепочку.

function getgrgid(gid) -
.gr.init() if (gid in .gr.bygid)
return .gr.bygid[gid] return "" ""

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

function getgruser(user) -
.gr.init() if (user in .gr.groupsbyuser)
return .gr.groupsbyuser[user] return "" ""

Функция getgruser не имеет двойника в Си. Она получает имя пользователя и возвращает список групп, членом которых является названный пользователь.

function getgrent() -
.gr.init() if (++.gr.count in .gr.bycount)
return .gr.bycount[.gr.count] return "" ""

Функция getgrent просматривает базу последовательно шаг за шагом. Она использует .gr.count для слежения за своей позицией в списке.

function endgrent() -
.gr.count = 0 ""

endgrent устанавливает .gr.count на ноль так что getgrent может стартовать опять с начала. Так же как с подпрограммами пользовательской базы данных, каждая функция вызывает .gr.init для инициализации массивов. Делая так, она тратит лишний прогон grcat, если эти функции используются (в противоположность варианту с переносом тела .gr.init в правило BEGIN). Большинство работы состоит в сканировании базы данных и построении различных связанных массивов. Функции, которые вызывает пользователь, сами по себе очень просты, они используют в своей работе ассоциативные массивы awk.

Программа id в разделе 16.1.3 [Печать выходной пользовательской информации], стр.215, использует эти функции.

15.13 Наименование глобальных переменных библиотечных функций

В начало страницы

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

Библиотечные функции часто нуждаются в глобальных переменных, которые они могут использовать, чтобы сохранить состояние информации между вызовами функции. Например, переменная .opti функции getopt (см, раздел 15.10 [Обработка параметров командной строки], стр., 186), и массив .tm.months, используемый функцией mktime (см. раздел 15.7 [Превращение даты в отметку времени], стр. 177). Такие переменные называются частными, поскольку их используют только функции из библиотеки.

Когда пишут библиотечную функцию, нужно стараться так выбрать имена для частных переменных, чтобы не было конфликтов с любыми другими переменными, используемыми другими библиотечными функциями или главной программой пользователя. Например, имя вроде `i' или `j' не является хорошим выбором, потому что пользовательские программы часто используют подобные имена для собственных целей.

В примерах программ, приведенных в этой главе, имена частных переменных начинаются с символа подчеркивания (`.'). Пользователи обычно не употребляют ведущих подчеркиваний в именах их переменных, так что такое соглашение сразу уменьшает шансы того, что это имя случайно встретится в пользовательской программе. Кроме того, некоторые из библиотечных функций используют префикс, который помогает указывать, что функция или группа функций используют эту переменную. Например, .tm.months в mktime (см. раздел 15.7 [Превращение дат в отметки времени], стр. 177), и .pw.byname в подпрограммах для пользовательских баз данных (см. раздел 15.11 [Чтение пользовательских баз данных], стр. 192). Мы рекомендуем пользоваться этим соглашением, поскольку оно еще больше уменьшает возможность случайных конфликтов в именах переменных. Заметим, что это соглашение можно использовать не только для имен переменных, но также и для частных имен функций.

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

Как заключительная нота в наименовании переменных, если функция делает глобальные переменные доступными для использования в главной программе, хорошо начинать такие переменные с заглавной буквы. Например, переменные Opterr и Optind в функции getopt (см. раздел 15.10 [Обработка параметров командной строки], page 186). Ведущие заглавные буквы указывают , что переменные --- глобальные, а тот факт, что не все буквы заглавные, показывает, что это не встроенная переменная awk, подобная FS.

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

function lib.func(x, y, l1, l2) -
... использует переменную some.var # some.var может быть локальной ...
# но это не очевидно с первого взгляда ""

Другое соглашение, обычное в объединении Tcl, состоит в использовании отдельного ассоциативного массива для запоминания значений, нужных для библиотечных функций или для "пакета." Это значительно уменьшает количество фактически используемых глобальных имен. Например, функции, описанные в разделе 15.11 [Чтение пользовательской базы данных], стр. 192, могут использовать PW.data["inited"], PW.data["total"], PW.data["count"] и PW.data["awklib"] вместо .pw.inited, .pw.awklib, .pw.total и .pw.count.

Предлагаемые в этом разделе соглашения есть только советы. От вас не требуется писать программы только так, мы только рекомендуем это.

В начало страницы

<<< Оглавление Страницы: 15  16 >>>