Язык программирования awk

Введение

Язык AWK был разработан и реализован как утилита UNIX'а в 1977 году Э.Ахо, П.Вайнбергером и Б.Керниганом. AWK — специализированный язык, предназначенный для обработки структурированных текстовых документов. Он соединяет в себе основные свойства и возможности утилит sed и grep, но одновременно является простым и удобным языком программирования, пригодным для решения широкого спектра простых повседневных задач. Широко известный язык Perl является прямым потомком языка AWK.

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

В этом тексте существенные места, описывающие тонкие детали и возможные ловушки,

выделены вот таким вот образом

а примеры кода

выделены вот так

В качестве интерпретатора языка awk обычно используется GNU awk , называемый также gawk. Запуск интерпретатора для обработки файла file в соответствии с программой prog осуществляется командой

gawk -f prog file ...

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

Название интерпретатора читается как английское слово oak (дуб) т.е. «оук», но ни в коем случае не как «авк» и не как «авэка»!

Структура программы

Программа представляет собой последовательность правил вида

селектор { действие }

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

Допустимы следующие частные случаи правил:

правило особенность что происходит
{ действие } отсутствует селектор действие выполняется для всех строк
селектор отсутствует действие все строки, удовлетворяющие селектору, печатаются
BEGIN { действие } специальный селектор действие выполняется до обработки первой строки
END { действие } специальный селектор действие выполняется после обработки последней строки

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

Разбор строки

Прочитанная строка обрабатывается и производится инициализация следующих предопределенных переменных:

переменная значение
FILENAME имя текущего входного файла
NR номер текущей строки
FNR номер текущей строки в текущем входном файле
NF число полей в текущей строке
$0 вся прочитанная строка
$n поле с номером n (n — число, переменная или выражение)

В выражении $n индексирование полей входной строки производится начиная с единицы.

Из входного потока последовательно выбираются строки. Разделитель строк задается в переменной RS.

Обработка строки состоит в разбиении ее на поля. По умолчанию считается, что поля отделены друг от друга одним или несколькими пробельными символами (пробелами или символами табуляции). В общем случае строка разбивается на поля, отделенные друг от друга разделителем полей. Разделитель полей задается переменной FS. Значение этой переменной может быть присвоено в секции BEGIN. Также можно использовать опцию '-F' в которой задается значение разделителя полей. Разелитель полей может быть регулярным выраженем. Разделитель полей, состощий только из пробельных символов, интерпретируется специальным образом — в этом случае разделителем полей считается любая последовательность пробельных символов. Во всех остальных случаях разделителем полей считается подстрока, удовлетворяющая регулярному выражению (в это правило также попадает случай, когда разделитель полей содержит только обычные символы, как минимум один из которых отличен от пробела). Если разделитель полей равен пустой строке, то происходит посимвольное разделение входной строки на односимвольные поля.

переменная интерпретация
RS разделитель строк при разборе входного потока
FS разделитель полей при разборе входной строки

Переменным, начинающимся с символа $, можно присваивать значения. При изменении значения одной из переменных $j (при j > 0) происходит также пересчет значения переменной $0.

Константы

Численные константы — это произвольное вещественное число, в котором можно использовать десятичный порядок (например 12e-3).

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

запись интерпретация
\\ символ обратной косой черты
\" символ двойной кавычки
\n новая строка
\r перевод строки
\xddd задание символа в шестнадцатеричном виде

Также в awk есть константы типа «регулярное выражение», записываемые между символами косой черты:

/регулярное_выражение/

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

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

Соответственно, регулярное выражение /\*/ придется записать как "\\*", поскольку уже при разборе строки как строковой константы последовательность "\\" заменится на символ "\".

Переменные

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

Если переменная участвует в арифметической операции (например a + b), то она приводится к числовому типу. Строка, которую невозможно интерпретировать как число, приводится к числу нуль. Если переменная участвует в строковой операции (например в операции «пробел», выполняющей конкатенацию строк), то она преобразуется в строку.

Рассмотрим интересный пример, в котором в качестве ответа будет напечатано число 27:

two = 2; three = 3
print (two three) + 4

Число можно принудительно превратить в строку, произведя его конкатенацию с пустой строкой; строку можно превратить в число, прибавив к ней ноль:

str_val = "" int_val
int_val = 0 + str_val

Преобразование числа в строку управляется предопределенной строковой переменной CONVFMT, имеющей по умолчанию значение "%.6g".

В логических выражениях истиной считается любое ненулевое число или любая непустая строка. Строковое значение "0" преобразуется в логическое значение "истина"!

Операции

+, -, *, /, %, ++, — арифметика
^ возведение в степень
+=, -=, *=, /=, %= инкрементная арифметика
^= возведение в степень
!, ||, && логика
пробел конкатенация строк
<, <=, ==, !=, >=, > сравнения
= присвоение
? : условное выражение

Операция "пробел" всегда работает со строковыми операндами. Сравнение выполняется как числовое сравнение только если оба операнда числовые, а при сравнении операндов смешанного типа оба операнда преобразуются в строку.

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

print 12 " " -12

напечатает '12-12' вместо ожидаемого '12 -12', поскольку сначала будет вычислено выражение '" " -12', в котором строка, состоящая из одного пробела (т.е. неприводимая к числу) будет преобразована в число 0.

Рассмотрим пример:

a = 2; b = " +2"
a == b             # Здесь производится строковое сравнение (результат -- ложь)

Селекторы

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

В первом случае считается, что строка удовлетворяет селектору, если логическое выражение истинно. Во втором случае селектору удовлетворяют все строки между строкой, для которой истинно первое выражение и строкой, для которой истинно второе выражение (включая сами эти строки).

В выражении могут также присутствовать шаблоны вида /…/. В этом случае считается, что шаблон принимает значение «истина», если текущая строка содержит шаблон.

Шаблоны

В тексте программы шаблон обычно задается в виде регулярного выражения, заключенного в символы '/' (т.н. литерал регулярного выражения вида /…/). Для сопоставления строк с регулярными выражениями используются следующие операции:

s ~ r проверить, что строка s сопоставляется с регулярным выражением r
s !~ r проверить, что строка s не сопоставляется с регулярным выражением r

Таким образом, выражение s ~ /re/ истинно если строка s содержит шаблон re.

Если регулярное выражение вида /re/ встречается в произвольном месте в тексте программы, то в этом случае оно эквивалентно выражению $0 ~ /re/.

Следующие символы имеют в регулярном выражении специальный смысл:

\ экранирование символа
^ начало строки
$ конец строки
. произвольный символ
? 0 или 1 вхождение символа
* 0 или более вхождений символа
+ 1 или более вхождений символа
{n} n вхождений строки
{n,} не менее n вхождений строки
{n,m} от n до m (включительно) вхождений строки
() группирование
| альтернатива
[…] список символов

В списке символов можно использовать диапазоны: [a-z] и комбинировать диапазоны и списки: [abf-zABF-G]. Символ "^", стоящий на первом месте в списке символов означает инвертирование списка т.е. такой список будет сопоставлен с любым символом, в него не входящим. Символы "\", "]", "-" и "^" в списке символов обладают специальным смыслом и должны экранироваться символом \. Остальные специальные символы в списке символов теряют свой специальный смысл и не должны экранироваться.

Также в списке символов можно использовать следующие классы символов:

[:blank:] пробел или табуляция
[:alpha:] буква
[:digit:] цифра
[:xdigit:] 16-ричная цифра
[:alnum:] цифра или буква
[:lower:] буква в нижнем регистре
[:upper:] буква в верхнем регистре
[:punct:] знаки пунктуации

Шаблоны, специфичные для GNU awk

Кроме шаблонов, включающих символы, соответствующие стандарту POSIX Extended Regular Expressions (POSIX EREs), GNU awk поддерживает шаблоны, включающие некоторые дополнительные возможности.

\w символ, образующий слово (буква, цифра или подчеркивание)
\W символ, не образующий слово
\< начало слова
\> конец слова
\y граница (начало или конец) слова
\B промежуток между символами, входящими в слово

Операторы

Комментарий начинается с символа "#" и продолжается до конца строки. Операторы можно отделять друг от друга как символом ";", так и переводом строки.

Доступна большая часть обычных операторов языка C:

break           выход из цикла
continue        переход к следующей итерации цикла

if (условие) {операторы} [else {операторы}]
while (условие) {операторы}
do {операторы} while (условие)
for (выражение; условие; выражение) {операторы}

Вывод

print ...                              вывод
print ... >file                        вывод в файл
printf fmt, ...                        форматный вывод
printf fmt, ... >file                  форматный вывод в файл

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

При выводе числа на печать используются следующие предопределенные переменные:

переменная интерпретация значение по умолчанию
OFMT формат вывода чисел %.6g
OFS разделитель полей (разделенных запятой) пробел
ORS разделитель записей (строк) перевод строки

Оператор print без аргументов выводит значение переменной $0, а для вывода пустой строки нужно написать

print ""

т.е. явно вывести пустую строку.

В операторах print и printf на месте file может находиться любое (строковое) выражение, интерпретируемое как имя файла. Вместо значка ">" может стоять значок "»", что означает открытие существующего файла на добавление.

В awk все файлы идентифицируются именами. При первом упоминании файла он открывается и остается открытым. Если в дальнейшем в некоторой файловой операции снова упоминается файл с этим именем, то операция проводится над уже открытым файлом. Закрыть файл можно явно, вызвав функцию close(имя_файла).

Массивы

Любая переменная, использованная в контексте массива (например x[12]) становится массивом. Все массивы в awk ассоциативные т.е. индексом может быть любая строка или число. В действительности все индексы массивов в awk строковые, а числовые индексы при индексировании предварительно преобразуются в строку с помощью форматной строки CONVFMT. При индексировании целыми числами такое преобразование не приводит к проблемам, но в случае использования вещественных индексов надо соблюдать осторожность!

Операции, доступные для массивов:

for(индекс in массив) {операторы}     перебор элементов массива

idx in array                 проверка принадлежности множеству индексов массива
(idx1, idx2, ...) in array

delete array[index]          удаление элемента массива
delete array                 удаление массива

Если имя переменной уже использовалось как массив и затем этот массив был удален, то это имя уже нельзя использовать в качестве имени обычной переменной.

В операторе for( idx in array ) индекс пробегает все значения, которыми индексировался массив. В выражении idx in array проверяется, принадлежит ли выражение idx ко множеству индексов массива array.

В многомерных массивах индексы можно перечислять в квадратных скобках через запятую (например x[idx1, idx2]). При этом фактическим индексом становится результат конкатенации индексов с разделителем, равным значению предопределенной переменной SUBSEP — сепаратора индексов в многомерных массивах с умалчиваемым значением "\034".

Фактически выражение x[idx1, idx2] эквивалентно выражению x[idx1 SUBSEP idx2]. При переборе элементов массива они перебираются как элементы одномерного массива, индексируемого индексами вида idx1 SUBSEP idx2 SUBSEP …. В результате для получения отдельных индексов приходится использовать функцию split() со значением аргумента sep, равным SUBSEP.

Числовые функции

sin(x), cos(x), atan2(y,x)
exp(x), log(x), sqrt(x)     математика [atan2(y,x) = atan(y/x)]
int(x)                      целая часть числа

rand()                      вещественное случайное число (0 < x < 1)
srand(n)                    установка стартового значения для rand()
srand()                     установка случайного стартового значения для rand()

Строковые функции

Индексирование символов в строке всегда осуществляется с единицы!

length(s)                   длина строки
substr(s, pos, len)         подстрока строки s из n символов начиная с позиции pos
substr(s, pos)              подстрока строки s начиная с позиции pos до конца строки
index(s, ss)                позиция подстроки ss в строке s или 0 если ss не найдена

match(s, re)                возвращает индекс самого левого вхождения шаблона re в строку s
                              или 0 если вхождение не найдено
sub(re, repl, str)          в строке str заменить самое левое вхождение шаблона re
                              на строку repl
gsub(re, repl, str)         в строке str заменить все вхождения шаблона re на строку repl

sprintf(fmt, ...)           сформировать строку по формату

split(s, fld, sep)          разбиение строки s на поля разделителем sep
split(s, fld)               разбиение строки s на поля

tolower(s), toupper(s)      приведение строки к нижнему или верхнему регистру

close(file)                 закрыть файл или канал
fflush()                    вытолкнуть буфер файла или канала
system(cmd)                 выполнить команду cmd

Функция split() помещает поля в массив fld и возвращает число полей.

Элемента массива fld, формируемого функцией split() индексируются с единицы.

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

Функция match() производит инициализацию следующих переменных:

RLENGTH длина найденной строки или -1, если строка не найдена
RSTART позиция найденной строки или 0, если строка не найдена

Функции sub() и gsub() возвращают число сделанных замен (0 или 1 в случае sub()). В аргументе repl можно использовать специальный символ "&", который заменяется на подстроку строки str, совпавшую с регулярным выражением. Символ "&" экранируется с помощью обратной косой черты (backslash).

Если аргумент repl задается в виде строковой константы, то следует иметь в виду, что такая константа будет разобрана дважды — один раз как строковая константа и второй — как строка замены. Если в строке разбора должен встретиться заэкранированный символ "&" т.е. строка "\&", то в строковую константу придется поместить последовательность "\\&".

Управление разбором файла

exit                 завершить выполнение программы
next                 перейти к обработке следующей строки
nextfile             переход к обработке следующего входного файла

getline              чтение и разбор следующей строки
(getline buf)        чтение следующей строки в переменную buf
(getline <file)      чтение и разбор следующей строки из файла file
(getline buf <file)  чтение следующей строки из файла file в переменную buf

Поведение оператора exit зависит от того, в каком блоке он размещен. Во всех случаях производится прерывается выполнение текущего правила и всего процесса сканирования входного потока строк. Если выполнение оператора exit происходит в блоке BEGIN, то выполнение этого блока прерывается, сканирование входного потока не производится и выполняется блок END. Если выполнение оператора exit происходит в обычном блоке, то прерывается выполнение этого блока и выполняется блок END. Если выполнение оператора exit происходит в блоке END, то выполнение программы производится немедленно.

При выполнении оператора next прерывается процесс сопоставления текущей строки с селекторами, читается следующая входная строка и для нее начинается процесс сопоставления с селекторами, начиная с первого правила программы.

Оператор nextfile работает аналогично оператору next, но прерывает обработку текущего входного файла и производит принудительный переход к обработке следующего входного файла из заданных в командной строке.

Функция getline возвращает флаг успешности: (-1), если произошла ошибка, 0, если достигнут конец файла и единицу, если чтение прошло успешно. В качестве параметра file можно указать любое выражение или строковую константу, которые будут интерпретироваться как имя файла (см. замечание о работе с файлами в разделе "Вывод").

Определение функций

function func_name( param_list )
{
  func_body
}

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

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

return            выход из функции
return val        возврат значения val

Библиотеки функций в awk

Опция -f prog также может быть повторена с различными значениями prog:

gawk -f prog1 -f prog2 ... file ...

При этом различные prog читаются последовательно и рассматриваются как одна программа. Секции BEGIN и END выполняются последовательно в том порядке, как они встречаются при последовательном чтении файлов. Эта особенность обычно используется для подключения библиотек awk-функций, поскольку других возможностей для этого не предоставляется.

Задание параметров при запуске

Задать значения параметров при запуске awk-скрипта проще всего с помощью опции -v, за которой может следовать оператор присваивания вида var = val:

gawk -f awkscript -v var=val file ...

В этом случае переменной var присваивается значение val еще до выполнения секции BEGIN.

За опцией -v может следовать только одно присваивание, но самих опций может быть несколько. Все опции -v должны быть расположены до первого имени обрабатываемого файла.

Также можно задавать значения переменных непосредственно в списке обрабатываемых файлов с помощью аргумента вида var=val. Значения таких переменных недоступны в блоке BEGIN. Присваивания и обработка файлов производятся последовательно слева направо, поэтому допустима следующая конструкция:

gawk -f script width=12 file1 width=20 file2

В этом случае при обработке файла file1 переменная width будет иметь значение 12, а при обработке файла file2 она примет значение 20.

Также awk-скрипт имеет доступ к аргументам командной строки и переменным окружения:

ARGC            число аргументов при запуске
ARGV            массив аргументов
ENVIRON         массив переменных окружения, индексируемый именем переменной

Программы со встроенной логикой

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

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

Работа с каналами

Функция system() — не единственный и не всегда лучший способ запуска процессов из awk-программы. Часто удобнее пользоваться выводом в канал и вводом из канала.

print ... | cmd              вывод в канал
printf fmt, ... | cmd        форматный вывод в канал

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

(cmd | getline)              ввод из канала
(cmd | getline buf)          ввод из канала в переменную

Эти операторы запускают команду cmd в отдельном процессе, связывают с его стандартным выводом канал и производят ввод из этого канала. Во втором операторе производится ввод в переменную buf.

Расширения GAWK

Ниже перечислены расширения, характерные только для GNU awk (gawk). Все эти расширения доступны по крайней мере для программ gawk версии не ниже, чем 3.0 (версию программы можно получить с помощью опции —version).

AWKPATH     путь поиска исходных файлов (переменная окружения);
systime()   получение системного времени;
strftime()  форматный вывод значения времени;
gensub()    обобщенная функция для замены текста;

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

Функции systime(), strftime() и gensub() описаны в следующих разделах.

В разделе "Отладка awk-скриптов" описаны дополнительные возможности интерпретатора gawk, полезные для обнаружения ошибок в awk-скриптах

Работа со временем

Функция systime() возвращает текущее системное время в секундах с момента Midnight, January 1, 1970, UTC (Midnight = 03:00:00).

Функция strftime() форматирует время, заданное в секундах с момента Midnight, January 1, 1970, UTC и возвращает строку, содержащую текстовое представление времени в соответствии со строкой формата:

strftime([format [, timestamp]])

Если пропущен аргумент timestamp, то выводится текущее системное время. Если опущен аргумент format, то используется строка формата "%a %b %d %H:%M:%S %Z %Y".

%%      символ `%'

%Z      наименование зоны

%d      номер дня (01-31)
%w      номер дня недели (0--6) [воскресенье = 0]
%a      название дня недели
%A      полное название дня недели
%m      номер месяца (01--12)
%b      название месяца
%B      полное название месяца
%y      2-значный номер года (00--99). 
%Y      4-значный номер года (например, 1995). 

%H      номер часа (00-23)
%I      номер часа (00-12)
%p      значок AM/PM соответствующий текущему времени
%M      номер минуты (00--59)
%S      номер секунды (00--61)

%X      время
%x      дата
%c      время и дата

%j      номер дня в году (001--366). 
%U      номер недели в году (00--53)
%W      номер недели в году (00--53)

Форматная спецификация %U выводит номер недели в году в предположении, что первое воскресенье года является первым днем недели номер 1, а спецификация %W — в предположении, что первый понедельник года является первым днем недели номер 1.

gensub(): обобщенная функция для замены текста

Функция gensub() в целом аналогична функции gsub():

gensub(regexp, repl, how, target)

Функция отыскивает в строке target вхождения регулярного выражения regexp и заменяет их на строку repl. В отличие от функций sub() и gsub() эта функция не изменяет исходную строку, а возвращает результат замены.

Если how является строкой, начинающейся с символа "g" или "G", то производится замена всех подстрок (аналогично gsub()), в противном случае аргумент how должен быть числом, определяющим количество производимых замен. Если аргумент how является строкой, не начинающейся с символа "g" или "G", или если значение числового аргумента how меньше нуля, то производится одна замена.

Второе отличие от функций sub() и gsub() — возможность работы с помеченными регулярными выражениями. Регулярное выражение называется помеченным, если в нем содержатся подвыражения, заключенные в круглые скобки. Такие подвыражения считаются занумерованными с единицы, а всему регулярному выражению соответствует нулевой индекс. В строке repl последовательность "\n", где n — номер подвыражения, соответствует подстроке исходной строки, поставленной в соответствие подвыражению с номером n. Таким образом, специальный символ "\0" соответствует всему регулярному выражению и эквивалентен символу "&" в функциях sub() и gsub(). Сам символ "\" должен быть экранирован: "\\".

Как и в функциях sub() и gsub(), строка замены, заданная строковой константой, сначала интерпретируется как строковая константа и лишь затем как строка замены. Соответственно для вставки специального символа "\1" следует использовать последовательность "\\1".

Отладка awk-скриптов

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

Вот пример такой ошибки:

fname = "filename"
prinf fname

Здесь в имени оператора printf пропущена буква "t", в результате чего во второй строке вместо вывода значения переменной fname происходит конкатенация значения переменной prinf со значением переменной fname. Поскольку переменная prinf не определена в момент использования, она имеет пустое значение (""), а результат конкатенации не используется, что допускается синтаксисом awk.

Полезное средство для обнаружения таких ошибок — возможность записи дампа всех глобальных переменных, обеспечиваемая опцией —dump-variables:

gawk --dump-variables -f prog file ...

В этом случае имена всех глобальных переменных и их типы будут выведены в файл с именем awkvars.out. Для числовых и строковых переменных будут также выведены их значения. Можно задать альтернативное имя для файла дампа:
gawk --dump-variables=dumpfilename -f prog file ...

Вот пример дампа, выводимого для пустой программы:

ARGC: number (1)
ARGIND: number (0)
ARGV: array, 1 elements
BINMODE: number (0)
CONVFMT: string ("%.6g")
ERRNO: number (0)
FIELDWIDTHS: string ("")
FILENAME: string ("")
FNR: number (0)
FS: string (" ")
IGNORECASE: number (0)
LINT: number (0)
NF: number (0)
NR: number (0)
OFMT: string ("%.6g")
OFS: string (" ")
ORS: string ("\n")
RLENGTH: number (0)
RS: string ("\n")
RSTART: number (0)
RT: string ("")
SUBSEP: string ("\034")
TEXTDOMAIN: string ("messages")

Другой метод отладки связан с режимом вывода предупреждений, который включается опцией —lint:

gawk --lint -f prog file ...

В качестве примера рассмотрим вывод, генерируемый в этом режиме при обработке приведенной выше программы:

gawk: _error.awk:3: warning: reference to uninitialized variable `prinf'

Это — наиболее полезное предупреждение, указывающее места использования неинициализированных переменных.

Некоторые идиомы языка awk

Имплицитное преобразование регистра

Напомним, что переменным $1, $2, …, можно присваивать значения, как и обычным переменным. и при этом происходит также пересчет значения переменной $0. На этом свойстве основана следующая идиома, позволяющая имплицитно преобразовать все выделенные поля к требуемому регистру:

{
    for( i=0; i<=NF; i++ )
      $i=toupper($i)
}

Проверка существования файла

Следующая идиоматическая конструкция позволяет проверить факт существования файла:

function exists(file, _dummy, _ret)
{
  _ret=0;
  if( (getline _dummy <file) >= 0 ){
    _ret = 1;
    close(file);
  }
  return _ret;
}

Здесь использовано необычное условие (getline _dummy <file) >= 0, поскольку в случае существования файл вполне может оказаться пустым.

Эксклюзивная обработка первого файла

Использование переменной FILENAME позволяет организовать независимую обработку первого из обрабатываемых файлов:

BEGIN {
  first_file = ""
}
first_file == "" {
  rulesfile = FILENAME;
}
FILENAME == first_file {
  process_first_file($0);
}
FILENAME != first_file {
  process_file($0);
}

Основные недостатки языка awk

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

Все переменные в awk имеют глобальную область видимости
В результате приходится следить за тем, чтобы в всей программе, включая функции, использовались уникальные имена переменных. Единственная возможность использовать локальные переменные (фиктивные аргументы функции) на практике слишком неудобна.
Переменная создается в момент первого использования
Даже если переменной еще не присвоено значение, ее значение можно использовать в выражении. В результате ошибки в написании имени переменной не приводят к появлению ошибок при выполнении скрипта.
Массивы и функции не являются полноправными объектами
Ссылку на функцию нельзя присвоить переменной, поместить в массив или вернуть из функции в качестве возвращаемого значения. Массив также не может быть возвращаемым значением функции. Кроме того, переменная, став однажды массивом, навсегда сохраняет это свойство т.е. даже после удаления оператором delete она не может принять скалярного значения.

Ссылки

Среди современных реализаций интерпретатора AWK наиболее популярны 'GNU Awk' (gawk) и 'One True Awk' (nawk или awk95). Обе реализации доступны для пользователей платформы win32. Реализацию 'One True Awk' можно скачать со странички книги The AWK Programming Language by Alfred V. Aho, Brian W. Kernighan, and Peter J. Weinberger В случае GNU Awk известно множество различных его 'портов' на платформу win32, но наиболее удачными являются проекты unxutils [http://unxutils.sourceforge.net и gnuwin32 [http://sourceforge.net/projects/gnuwin32].

Из описаний можно рекомендовать замечательное руководство 'The GNU Awk User's Guide' Перевод этой книги на русский язык (Эффективное программирование на языке AWK. Руководство пользователя для GNU Awk) также доступен в сети [http://www.math.spbu.ru/user/rus/cluster/Doc/Library/awk_baluev/awk_bal_oglav.shtml. Достаточно полное описание языка AWK содержится также в книге 'Sed and Awk' Также может быть интересен FAQ из newsgroup 'comp.lang.awk' [http://www.faqs.org/faqs/computer-lang/awk/faq.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License