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

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

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

Оглавление

Эта глава представляет библиотеку полезных функций awk. В примерах программ, представленных ниже, (см. главу 16 [Практические awk-программы], стр.205) используются эти функции. Функции здесь представлены в порядке от простых к сложным. Раздел 16.2.7 [Извлечение программ из файлов Texinfo Source], стр. 238, представляет программу, которую можно использовать для извлечения исходного кода этих примеров библиотечных функций и программ из Texinfo source для этой книги. (Это было уже сделано как часть распространения gawk.

Если вы написали одну или более полезных awk-функций общего назначения и готовы предложить их для издания в этой книге, вступите в контакт с ее автором. См. раздел B.7 [Сообщения о проблемах и ошибках], стр. 292, для информации о том, как это сделать. Не ограничивайтесь только посылкой кода, когда вам предложат поместить его в область публичного доступа, опубликовать в рамках GPL (см. [GNU GENERAL PUBLIC LICENSE], стр. 309), или передать copyright в Free Software Foundation.

15.1 Моделирование специальных свойств gawk

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

Программы в этой главе и в главе 16 [Практические программы awk], стр. 205, используют возможности, специфичные для gawk. В этой главе коротко обсуждается, как вы можете переписать эти программы для других различных реализаций awk.

Диагностические сообщения об ошибках посылаются в `/dev/stderr'. Пишите `-- "cat 1?&2"' вместо `? "/dev/stderr"', если ваша система не имеет `/dev/stderr', или если не можете использовать gawk.

Некоторые программы используют nextfile (см. раздел 9.8 [Оператор nextfile], стр. 112), для пропуска остатка ввода в входном файле. Раздел 15.2 [Реализация nextfile как функции], стр. 170, показывает, как написать функцию, которая делает то же самое.

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

# ignore case - $0 = tolower($0) ""

Проверьте также, что все константы regexp и строковые константы, используемые в сравнениях, состоят только из букв нижнего регистра.

15.2 Реализация nextfile как функции

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

Оператор nextfile, описанный в разделе 9.8 [Оператор nextfile], стр. 112, есть специальное расширение gawk. Он недоступен в других реализациях awk. В настоящем разделе приведены две версии функции nextfile, которые можно использовать для моделирования соответствующего оператора gawk, если невозможно использовать gawk.

Вот первая попытка написать функцию nextfile.

# nextfile --- пропустить оставшиеся записи в текущем файле
# это должно быть прочитано до "главной" программы awk
function  nextfile()
- .abandon. = FILENAME; next "" .abandon. == FILENAME - next ""

Этот файл должен быть вставлен перед главной программой, потому что он содержит правило, которое должно выполняться c самого начала. Это правило сравнивает имя текущего файла данных (которое всегда находится в переменной FILENAME) с индивидуальной переменной, названной .abandon.. Если имя файла соответствует, то действие правила выполняет оператор next, то есть перейти к следующей записи. (Употребление `.' в имени перемененной есть соглашение. Оно подробнее обсуждается в разделе 15.13 [Наименование глобальных переменных библиотечных функций], стр. 202). Использование оператора next фактически создает цикл, который читает все записи из текущего файла с данными. В итоге достигается конец файла и открывается новый файл с данными, изменяющий значение FILENAME. Когда это произойдет, сравнение .abandon. с FILENAME выдает false и выполнение продолжается с первого правила "реальной" программы.

Сама функция nextfile просто устанавливает значение .abandon. и затем выполняет оператор next для запуска цикла.1

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


1 Некоторые реализации awk не позволяют вам выполнять next внутри тела функции. Необходимы какие-то другие приемы, если вы используете такую версию.

# nextfile --- пропустить оставшиеся в текущем файле записи
# правильно обрабатывать последовательные появления того же файла
# Arnold Robbins, arnold@gnu.org, Public Domain
# May, 1993
# это должно быть прочтено  до "главной" awk-программы
function nextfile()
- .abandon. = FILENAME; next "" .abandon. == FILENAME -
if (FNR == 1)
.abandon. = "" else
next ""

Действия nextfile не меняются. Она устанавливает .abandon. равным текущему имени файла и выполняет оператор next. Он читает следующую запись и увеличивает FNR, так что FNR получает по крайней мере значение два. Однако, если nextfile вызван для последней записи в файле, то awk закроет текущий файл с данными и перейдет к следующему. При этом FILENAME получит значение имени нового файла и FNR получит значение один. Если этот следующий файл есть тот же, что и предыдущий, .abandon. будет еще равно FILENAME. Однако, FNR будет равно единице, говоря нам, что мы имеем дело с новым файлом, не тем, который читали при выполнении функции nextfile. В этом случае .abandon. получает значение пустой цепочки, дальнейшего выполнения этого правила не происходит (пока nextfile не будет вызвана в следующий раз). Если же FNR имеет значением не единицу, то мы еще находимся в прежнем файле и программа выполняет оператор next для пропуска его конца.

Важным вопросом в этой точке будет такой: "если действия nextfile моделируются библиотечной функцией, зачем этот оператор встроен в gawk?" Это действительно важный вопрос. Дополнительные свойства при малой надобности приводят к более сложной и медленной программе, которую труднее поддерживать. Ответ состоит в том, что встраивание nextfile в gawk дает значительный выигрыш в эффективности. Если функция nextfile выполняется в начале большого файла, awk будет сканировать весь файл, расщепляя его на записи, чтобы пропустить его обработку. Встроенный оператор nextfile может просто закрыть немедленно файл и перейти к следующему, экономя много времени. Это особенно важно в awk, поскольку awk-программы обычно ориентированы на I/O (т.е. они большую часть времени тратят на ввод и вывод, а не на вычисления.

15.3 Контроль

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

При написании больших программ часто полезно быть в состоянии проверить, что некоторое условие или множество условий соблюдаются. Перед переходом к некоторым вычислениям следует проверить, что перед вами лежит именно нужный случай. Такие операторы называются "утверждениями" ("assertion"). В языке Си есть файлы с заголовком !assert.h? и соответствующие макро assert, которые программист может использовать для утверждений. Если утверждение ложно, макро assert печатает диагностическое сообщение, описывающее нарушенное условие, и убивает программу. В Си, использование утверждения выглядит так:

#include !assert.h?
int myfunc(int a, double b) -
assert(a != 5 && b ?= 17); ... ""

Если утверждение не выполнено, программа напечатает что-нибудь вроде:

prog.c:5: assertion failed: a != 5 && b ?= 17

Язык ANSI C дает возможность превратить условие в цепочку для печати диагностики. Это невозможно в awk, так что здесь функция assert требует цепочечную версию проверяемого условия.

# assert --- подтвердить что условие выполнено. Иначе exit.
# Arnold Robbins, arnold@gnu.org, Public Domain
# May, 1993
function assert(condition, string) -
if (! condition) -
printf("%s:%d: assertion failed: %s"n",
FILENAME, FNR, string) ? "/dev/stderr" .assert.exit = 1 exit 1 "" ""
END -
if (.assert.exit)
exit 1 ""

Функция assert проверяет параметр condition. Если оно дает false, она печатает сообщение в standard error, используя цепочку string для описания ошибки. Затем устанавливает переменную .assert.exit в единицу и выполняет оператор exit, который передает управление в правило END. Если правила END находят, что .assert.exit есть true, происходит немедленный выход.

Назначение правила END с тестом --- удержать от выполнения всякие другие END. Когда assertion выдает false, программа должна немедленно заканчиваться. Если ни один assertion не выдает fflse, то .assert.exit остается со значением false, когда правило END выполняется нормально и остаток программных правил END будет выполнен. Чтобы все это работало правильно, `assert.awk' должен быть первым входным файлом, прочтенным awk.

Вы должны использовать эту функцию в своих программах следующим образом:

function myfunc(a, b) -
assert(a != 5 && b ?= 17, "a != 5 && b ?= 17") ... ""

Если  assertion потерпит неудачу, вы получите сообщение:

mydata:1357: assertion failed: a != 5 && b ?= 17

Проблема с этой версией assert в том, что она может не сработать со стандартным awk. Правило END автоматически добавляется к программе, вызывающей assert. Обычно, если программа состоит только из правила BEGIN, то входные файлы и/или стандартный ввод не читаются. Однако теперь, когда программа имеет правило END, awk будет пытаться прочесть входные файлы с данными или стандартный ввод (см. раздел 8.1.5.1 [Стартовые и завершающие действия], стр. 100), очень вероятно, что программа зависнет, ожидая ввода.

15.4 Округление чисел

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

Способ округления в printf и sprintf (см. раздел 6.5 [Употребление операторов printf для декоративной печати], стр. 64) часто зависит от подпрограммы sprintf системы Си. На многих машинах sprintf округляет "unbiased " ("беспристрастно"), что означает, что округление при отбрасывании .5' не всегда происходит с избытком, в противоположность наивному ожиданию. При беспристрастном округлении `.5' округляется до четного, а не всегда с избытком, так что 1.5 округляется до 2, а 4.5 до 4. В результате если вы пользуетесь форматом без округления (например, "%.0f"), вы должны проверять, что делает ваша система. Следующая функция округляет традиционно; она может оказаться полезной, если ваша awk printf округляет беспристрастно.


# round --- делать нормальное округление
# # Arnold Robbins, arnold@gnu.org, August, 1996
# Public Domain
function round(x, ival, aval, fraction) -
ival = int(x) # целая часть, int() усекает
# проверка наличия дробной части
if (ival == x) # нет дробной части
return x
if (x ! 0) -
aval = -x # абсолютное значение ival = int(aval) дробное = aval - ival
if (fraction ?= .5) return int(x) - 1 # -2.5 --? -3
else return int(x) # -2.3 --? -2 ""
else - fraction = x - ival if (fraction ?= .5)
return ival + 1 else
return ival "" ""
# для проверки - print $0, round($0) ""

15.5 Перевод символов в числовой код

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

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

Обратная функция есть chr (аналогична функции с тем же именем в Паскале), которая получает число и возвращает символ. Обе функции могут быть просто написаны в awk; нет существенных причин для встраивания их в интерпретатор.

# ord.awk --- делает ord и chr # # глобальными идентификаторами:
# .ord.: численное значение символа
# .ord.init: функция инициализации .ord.
# # Arnold Robbins # arnold@gnu.org # Public Domain # 16 January, 1992
# 20 July, 1992, revised

BEGIN - .ord.init() "" function .ord.init( low, high, i, t) -
low = sprintf("%c", 7) # BEL есть ascii 7 если  (low == ""a")
- # regular ascii
low = 0 high = 127 "" else if (sprintf("%c", 128 + 7) == ""a") -
# ascii, отметка четности  low = 128 high = 255 "" else - # ebcdic(!)
low = 0 high = 255 ""
for (i = low; i != high; i++) -
t = sprintf("%c", i) .ord.[t] = i "" ""

Следует дать некоторые объяснения о числах, используемых в chr. Наиболее известное сейчас множество символов есть ASCII. Хотя восьмибитовый байт может иметь 256 различных значений (от нуля до 255), ASCII определяет только символы со значениями от 0 до 127. *2* По крайней мере один изготовитель компьютеров, которого мы знаем, использует ASCII с отмеченной четностью, означающей, что самый левый бит в байте имеет всегда значение 1. Это значит, что на таких системах


2 ASCII используется во многих странах для представления в значениях от 128 до 255 специальных символов этой страны. Если ваша система использует такое расширение, вы можете упростить .ord.init до простого цикла от 0 до 255.

символы имеют численное значение от 128 to 255. Наконец, большие универсальные системы используют систему символов EBCDIC, которая использует все 256 значений. Хотя на некоторых старых системах используются другие множества символов, о них не стоит говорить.

function ord(str, c) -
# интерес представляет только первый символ
c = substr(str, 1, 1) return .ord.[c] ""

function chr(c) -
# превращайте с в число добавлением 0
return sprintf("%c", c + 0) ""

#### test code #### # BEGIN " # - # for (;;) - # printf("enter a character: ")
 # if (getline var != 0) # break # printf("ord(%s) = %d"n", var, ord(var)) # ""
# ""

Очевидным усовершенствованием для этих функций было бы перенести код функции .ord.init в тело правила BEGIN. Это и было так написано первоначально для облегчения разработки. Имеется и "тестовая программа" в правиле BEGIN для проверки функции. Она превращена в комментарий для увеличения производительности.

15.6 Соединение компонент массива в цепочку

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

При обработке цепочек часто оказывается удобным соединить все цепочки в массиве в одну длинную цепочку. Следующая функция, join , выполняет эту задачу. Она используется далее в нескольких прикладных программах (см. главу 16 [Практические awk-программы], стр. 205).

Важно хорошо спланировать эту функцию; она должна быть общей, но должна также иметь разумное поведение по умолчанию. Она вызывается с массивом и начальным и конечным индексами для объединяемых элементов массива. Тем самым предполагается, что индексы являются числами -- разумное предположение, поскольку массив, вероятно, создавался с split (см. раздел 12.3 [Встроенные функции для действий с цепочками], стр. 137).

# join.awk --- соединить массив в цепочку
# Arnold Robbins, arnold@gnu.org, Public Domain # May 1993
function join(array, start, end, sep, result, i) -
if (sep == "")
sep = " " else if (sep == SUBSEP) # магическое значение
sep = "" result = array[start] for (i = start + 1; i != end; i++)
result = result sep array[i] return result ""

Необязательный дополнительный аргумент есть сепаратор для использования при соединении цепочек в одну цепочку. Если в вызове указано непустое значение, то join использует его. Если такового нет, он считается пустым. В таком случае join использует один пробел как сепаратор по умолчанию. Если указанное значение равно SUBSEP, то join соединяет цепочки без сепараторов между ними. SUBSEP служит как "магическое" значение для указания того, что не должно быть никаких сепараторов между цепочками-компонентами. Было бы очень хорошо, если бы awk имел оператор присваивания для конкатенации. Отсутствие явного оператора для конкатенации делает операции над цепочками более трудными, чем они должны быть на самом деле.

15.7 Превращения дат в отметки времени

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

Функция systime, встроенная в gawk, возвращает текущее время дня как отметку времени в секундах от начала века. Эта отметка может быть превращена в пригодную для печати дату в одном из множества форматов с помощью встроенной функции strftime. (Подробности о systime и strftime см. в разделе 12.5 [Функции для действий с отметками времени], стр. 148.)

Интересную и трудную проблему представляет превращение читаемого представления обратно в отметку времени. Библиотека ANSI Cи имеет функцию mktime, которая делает основную работу превращения канонического представления даты в отметку времени. С первого взгляда может показаться, что gawk должна иметь встроенную функцию mktime, которая копирует версию языка Си.

Приведем версию mktime для awk. Она берет простое представление даты и времени и сворачивает их в метку времени. Код, представленный здесь, перемешан с словесными пояснениями. В разделе 16.2.7 [Извлечение программ из файлов Texinfo Source], стр. 238, вы увидите, как должен обрабатываться файл Texinfo source этой книги для извлечения кода в отдельный исходный файл.

Программа начинается с описывающего комментария и правила BEGIN, которое инициализирует таблицу .tm.months. Эта таблица есть двумерный массив, содержащий длины месяцев. Первый индекс есть 0 для регулярных годов и 1 для високосных. Значения длин одинаковы для годов обоих типов, за исключением Февраля; поэтому нужно использовать кратное присваивание.

# mktime.awk --- преобразует каноническое представление даты
# в отметку времени# Arnold Robbins, arnold@gnu.org, Public Domain # May 1993
BEGIN " -
# Инициализация таблицы длин месяцев
.tm.months[0,1] = .tm.months[1,1] = 31 .tm.months[0,2] = 28;
.tm.months[1,2] = 29 .tm.months[0,3] = .tm.months[1,3] = 31
.tm.months[0,4] = .tm.months[1,4] = 30 .tm.months[0,5] = .tm.months[1,5] = 31
.tm.months[0,6] = .tm.months[1,6] = 30 .tm.months[0,7] = .tm.months[1,7] = 31
.tm.months[0,8] = .tm.months[1,8] = 31 .tm.months[0,9] = .tm.months[1,9] = 30
.tm.months[0,10] = .tm.months[1,10] = 31
.tm.months[0,11] = .tm.months[1,11] = 30
.tm.months[0,12] = .tm.months[1,12] = 31 ""

Польза от смешивания кратных правил BEGIN (см. раздел 8.1.5 [Специальные образцы BEGIN and END], стр. 100) особенно очевидна при написании библиотечных файлов. Функции в библиотечных файлах правильно инициализируют их частные данные и также обеспечивают завершающие действия в частных правилах END.

Следующая простая функция вычисляет, является или нет данный год високосным. Если год точно делится на 4, но не делится на 100 или если он точно делится на 400, то это високосный год, 1900 не был, а 2000 будет високосным.

# определяет високосность года
function .tm.isleap(year, ret) -
ret = (year % 4 == 0 && year % 100 != 0) ----
(year % 400 == 0)
return ret ""

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

Следующая функция более интересна. Она делает большую часть работы timestamp, которая превращает дату и время в число секунд с начала века. Вызов передает массив, (не очень удачно названный a), содержащий 6 значений: год со столетиями, месяц как число между 1 и 12, день месяца, час как число между 0 и 23, минуты в часе и секунды в пределах одной минуты.

Функция использует несколько локальных переменных для предвычисления количества секунд в часе, секунд в дне и секунд в году. Часто в подобных Си-кодах просто пишут выражения друг за другом, ожидая, что компилятор заменит их константами. Например, большинство Си-компиляторов превратят `60 * 60' в `3600' во время компиляции вместо пере вычисления их каждый раз при выполнении программы. Предвычисление этих значений делает функцию более эффективной.

# перевести дату в секунды
function .tm.addup(a, total, yearsecs, daysecs,
hoursecs, i, j) -
hoursecs = 60 * 60 daysecs = 24 * hoursecs yearsecs = 365 * daysecs
total = (a[1] - 1970) * yearsecs
# лишний день для високосных годов for (i = 1970; i ! a[1]; i++)
if (.tm.isleap(i))
total += daysecs
j = .tm.isleap(a[1]) for (i = 1; i ! a[2]; i++)
total += .tm.months[j, i] * daysecs
total += (a[3] - 1) * daysecs total += a[4] * hoursecs
total += a[5] * 60 total += a[6]
return total ""

Функция сначала находит приближенное значение числа секунд между полночью 1 Января 1970 года *3* и началом текущего года. Затем просматривает эти годы и для каждого високосного года добавляет дневную порцию секунд. Переменная j есть 0 или 1 , если текущий год соответственно високосный или нет. Для каждого месяца в текущем году (до текущего месяца) добавляется число секунд в месяце с помощью соответствующего элемента в массиве .tm.months. Наконец, добавляется в секундах количество дней до текущего дня и часы, минуты и секунды текущего дня. В результате получаем количество секунд, истекших с 1 января 1970 года. Это значение --- еще не то, что нужно. Опишем коротко, почему.

Главная функция mktime берет один аргумент --- цепочку символов. Эта цепочка представляет дату и время в "канонической" форме. Она должна быть "год месяц день час минуты секунды".

 
# mktime --- превращение даты в секунды,
# компенсация на часовой пояс
function mktime(str, res1, res2, a, b, i, j, t, diff) -
i = split(str, a, " ") # не полагайтесь на  FS
if (i != 6)
return -1
# превращение в число
for (j in a) a[j] += 0

3 Это начало века в системах POSIX systems. Оно может быть другим в других системах.
# проверка
if (a[1] ! 1970 ---- a[2] ! 1 ---- a[2] ? 12 ---- a[3] ! 1 ---- a[3] ? 31
---- a[4] ! 0 ---- a[4] ? 23 ---- a[5] ! 0 ---- a[5] ? 59 ---- a[6] ! 0
---- a[6] ? 60 )
return -1
res1 = .tm.addup(a) t = strftime("%Y %m %d %H %M %S", res1)
if (.tm.debug)
printf("(%s) -? (%s)"n", str, t) ? "/dev/stderr"
split(t, b, " ") res2 = .tm.addup(b)
diff = res1 - res2 if (.tm.debug)
printf("diff = %d seconds"n", diff) ? "/dev/stderr"
res1 += diff return res1 ""

Функция сначала разделяет цепочку в массив, используя пробелы и tab в качестве сепараторов. Если в массиве оказываются не 6 элементов, она возвращает ошибку, указанную как значение \Gamma 1. Затем превращает каждый элемент массива в число, добавляя к ним 0. Следующий оператор `if' проверяет, находится ли каждый элемент в допустимых границах. (Эта проверка может быть расширена, например, проверкой того, что день месяца соответствует размерам указанного месяца.) Все это по существу представляет предварительную подготовку и проверку на ошибки.

Вспомним, что .tm.addup дает время в секундах с полночи 1 Января 1970 года. Эта величина не представляет непосредственно желаемый результат, поскольку вычисление не принимало в расчет часовой пояс. Другими словами, полученное значение представляет время в секундах, прошедшее с начала века, но только для UTC (Универсального координированного времени). Если местный часовой пояс лежит к востоку или западу от UTC, то некоторое количество часов должно быть добавлено или вычтено из полученной отметки времени.

Например, 6:23 p.m. в Атланте, Georgia (USA), нормально на 5 часов западнее (позже) UTC. Это только на 4 часа позже UTC, если введено декретное время. Если вы вызываете mktime в Атланте с аргументом "1993 5 23 18 23 12", результат от .tm.addup будет выдан для 6:23 p.m. UTC, что соответствует только 2:23 p.m. в Атланте. Необходимо добавить еще 4 часа в секундах для правильного результата. Как mktime может определить свое отличие от UTC? Это на удивление легко. Выданная отметка времени представляет время, переданное в mktime как UTC. Эта отметка должна быть передана опять в strftime, которая превратит ее в местное время; то есть, как будто разница с UTC уже добавлена к ней. Это делается посредством передачи "%Y %m %d %H %M %S" в strftime в качестве аргумента format. Она возвратит отмету времени в исходном строчном формате. Результат представляет время, которое учитывает разность с UTC. Когда новое время будет опять превращено в отметку времени, разность между двумя отметками будет разностью (в секундах) между местным временем и UTC. Эта разность добавляется к полученному ранее результату. Демонстрирующий это пример приведен ниже.

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

BEGIN -
if (.tm.test) -
printf "Enter date as yyyy mm dd hh mm ss: " getline .tm.test.date
t = mktime(.tm.test.date) r = strftime("%Y %m %d %H %M %S", t) printf
"Got back (%s)"n", r "" ""

Вся программа использует две переменных, которые могут быть заданы из командной строки, для управления отладочным выходом и запуска теста в конечном правиле BEGIN. Вот результат пропуска теста. (Заметим, что отладочный выход идет как стандартная ошибка, а выход теста направлен в стандартный выход).

$ gawk -f mktime.awk -v .tm.test=1 -v .tm.debug=1
a Enter date as yyyy mm dd hh mm ss: 1993 5 23 15 35 10
error (1993 5 23 15 35 10) -? (1993 05 23 11 35 10)
error diff = 14400 seconds
a Got back (1993 05 23 15 35 10)

Введенное время составляло 3:35 p.m. (15:35 по 24-часовым часам), 23 Мая 1993 года. Первая строка отладочного выхода показывает результат как время UTC-- на четыре часа перед локальным часовым поясом. Вторая строка показывает, что разность составляет 14400 секунд, т.е. четыре часа. (Разность только четыре часа, поскольку в Мае действует декретное время.) Последняя строка выхода теста показывает, что алгорифм компенсации на часовой пояс работает; возвращенное время совпадает с введенным.

Эта программа не решает общую проблему преобразования произвольного представления даты в отметку времени. Эта проблема очень запутанная. Однако функция mktime дает фундамент для ее решения. Другие программы должны заменять имена месяцев числами и время AM/PM в 24-часовые данные для генерирования канонического формата, требуемого mktime.

15.8 Представление текущего времени

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

Функции systime и strftime, описанные в разделе 12.5 [Функции для действий с метками времени], стр. 148, обеспечивают минимум усилий, необходимых для действий с временем в удобной для чтения форме. Хотя strftime и велика, управляющие форматы не необходимо запоминать или понимать при чтении программы.

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

# gettimeofday --- выдать время дня в обычном формате
# Arnold Robbins, arnold@gnu.org, Public Domain, May 1993
# # Возвращает цепочку в формате выхода  date(1)
# Заполняет массив аргументов времени индивидуальными значениями:
# time["second"] -- секунды (0 - 59) # time["minute"] -- минуты (0 - 59)
# time["hour"] -- часы (0 - 23) # time["althour"] -- часы (0 - 12)
# time["monthday"] -- день месяца (1 - 31)
# time["month"] -- месяц года (1 - 12)
# time["monthname"] -- имя месяца
# time["shortmonth"] -- сокращенное имя месяца
# time["year"] -- год в пределах века (0 - 99)
# time["fullyear"] -- год со столетиями (19xx or 20xx)
# time["weekday"] -- день недели (Воскресенье = 0)
# time["altweekday"] -- день недели (Понедельник = 0)
# time["weeknum"] -- номер недели, воскресенье первый день
# time["altweeknum"] -- номер недели, Понедельник первый день
# time["dayname"] -- имя дня недели
# time["shortdayname"] -- сокращенное имя дня недели
# time["yearday"] -- день в году (0 - 365)
# time["timezone"] -- сокращение для time
function gettimeofday(time, ret, now, i) -
# выдает время сразу, избегая не необходимых обращений к системе
now = systime()
# возвращает вывод типа  date(1)
ret = strftime("%a %b %d %H:%M:%S %Z %Y", now)
# чистка выходного массива
for (i in time) delete time[i]
# Заполнение его значениями,  числовые цепочки превращаются в числа
# добавлением  0
time["second"] = strftime("%S", now) + 0
time["minute"] = strftime("%M", now) + 0
time["hour"] = strftime("%H", now) + 0
time["althour"] = strftime("%I", now) + 0
time["monthday"] = strftime("%d", now) + 0
time["month"] = strftime("%m", now) + 0
time["monthname"] = strftime("%B", now)
time["shortmonth"] = strftime("%b", now)
time["year"] = strftime("%y", now) + 0
time["fullyear"] = strftime("%Y", now) + 0
time["weekday"] = strftime("%w", now) + 0
time["altweekday"] = strftime("%u", now) + 0
time["dayname"] = strftime("%A", now)
time["shortdayname"] = strftime("%a", now)
time["yearday"] = strftime("%j", now) + 0
time["timezone"] = strftime("%Z", now)
time["ampm"] = strftime("%p", now)
time["weeknum"] = strftime("%U", now) + 0
time["altweeknum"] = strftime("%W", now) + 0
return ret ""

Цепочки-индексы легче использовать и читать, чем различные форматы, требуемые для strftime. Программа alarm, представленная в разделе 16.2.2 [Программа-будильник], стр.228, использует эту функцию.

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

15.9 Отметка границ файлов с данными

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

Правила из BEGIN и END каждое вычисляются только один раз, в начале и в конце awk-программ (см. раздел 8.1.5 [Специальные образцы BEGIN и END], стр. 100). Мы (авторы gawks) однажды встретили пользователя, который ошибочно полагал, что правило BEGIN должно выполняться при начале каждого файла с данными, а END --- в конце каждого файла. Когда ему объяснили, что дело обстоит не так, оп попросил, чтобы в gawk добавили новые специальные образцы, называемые BEGIN.FILE END.FILE с желаемым им поведением. Он даже снабдил нас кодом, реализующим эти действия.

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

# transfile.awk # # Дает пользователю возможность переноса имени файла #
# Пользователь должен определить функции  beginfile() и endfile()
#каждая берет имя файла соответственно при старте или окончании
# # Arnold Robbins, arnold@gnu.org, January 1992 # Public Domain

FILENAME != .oldfilename " -
if (.oldfilename != "")
endfile(.oldfilename) .oldfilename = FILENAME beginfile(FILENAME) ""
END - endfile(FILENAME) ""

Этот файл должен быть загружен перед "главной" программой пользователя, так чтобы правило, которое он содержит, вычислялось первым. Это правило полагается на awk-переменную FILENAME, которая автоматически меняется для каждого нового файла с данными. Текущее имя файла сохраняется в частной переменной .oldfilename. Если FILENAME не равно .oldfilename, то обрабатывается новый файл с данными и необходимо вызывать endfile для старого файла. Поскольку endfile должен вызываться только когда обработка файла закончена, программа сначала проверяет, что .oldfilename не пуста.

Затем программа присваивает имя текущего файла переменной .oldfilename и вызывает beginfile для нового файла. Поскольку, подобно всем awk-переменным, .oldfilename инициализируется пустой строкой, это правило правильно выполняется и для первого файла с данными. Программа также имеет правило END для завершающей обработки последнего файла. Поскольку это правило END приходит перед всеми правилами END в "главной" программе, сначала будет вызвана endfile. И опять значение кратных правил BEGIN END должно быть ясно.

Эта версия сталкивается с теми же трудностями, что и первая версия nextfile (см. раздел 15.2 [Реализация nextfile в виде функции], стр. 170). Если один и тот же файл с данными встретится два раза подряд в командной строке, то endfile и beginfile не будут выполняться в конце первого прохода и в начале второго. Проблему решает следующая версия.

# ftrans.awk --- обрабатывает переходы файлов с данными
# # пользователь определяет функции beginfile() и endfile()
# # Arnold Robbins, arnold@gnu.org, November 1992 # Public Domain
FNR == 1 -
if (.filename. != "")endfile(.filename.)
.filename. = FILENAME beginfile(FILENAME) ""
END - endfile(.filename.) ""

В разделах 16.1.7 [Подсчеты], стр. 225, вы увидите, как эти библиотечные функции могут использоваться и как это упрощает написание главной программы.

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

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