Язык обработки структурированных текстов AWK

«AWK» — интерпретируемый Тьюринг-полный язык программирования, предназначенный для обработки текста и создания отчетов. Необычное название языка происходит от первых букв фамилий авторов — Alfred V. Aho, Peter J. Weinberger и Brian W. Kernighan.

«AWK» рассматривает входной поток как список записей. Программа на «AWK» представляет из себя набор правил вида «шаблон — действие», где шаблон является регулярным выражением, а действие — последовательностью команд или функций. Если шаблон не указан, то действие выполняется для любой записи. Если не указано действие, то запись выводится в выходной поток. Каждая запись поочерёдно сравнивается со всеми шаблонами, и каждый раз когда она соответствует шаблону, выполняется указанное действие. Каждая запись в свою очередь делится на поля. Упрощенно, если говорить об обычном тексте и не учитывать возможности настройки «AWK», то запись — это строка, а поле — это слово в строке.

Если сравнивать редактор SED и язык «AWK», то в первую очередь стоит вспомнить, что оба они являются Тьюринг-полными. Это даёт основание утверждать, что в теории любая задача может быть успешно решена как на «SED» так и на «AWK», вопрос только в том, сколько времени и нервов это займёт. «AWK» выводит обработку текстовых данных на более высокий уровень. Благодаря «AWK» в нашем распоряжении оказывается полноценный язык программирования, а не ограниченный набор команд, отдаваемых редактору. Математические операции, поддержка переменных и ассоциативных массивов, управляющие конструкции if-then, циклы и т.д. всё это поддерживается в «AWK» из коробки.

ОБРАЗЕЦ

GIST | Простой пример «AWK» — обычная читалка текстовых файлов с выводом на экран. При оформлении этого и последующих примеров будем придерживаться следующего порядка: сверху параметры командной строки, внизу стандартные потоки слева ввода stdin и справа вывода stdout:

'{print}'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809

Из полезных опций командной строки, которые можно указать при запуске «AWK», стоит выделить возможность указать символ-разделитель для полей -F и возможность объявить переменную и задать её значение по умолчанию -v var=value. Как видно из примера, команда print без дополнительных параметров печатает все содержимое текущей записи.

ТАБЛИЦЫ

GIST | «AWK» отлично справляется с файлами, структурированными предсказуемым образом. К примеру, эта команда особенно сильна в анализе и обработке табличных данных. По умолчанию для разделения полей «AWK» использует пробельные символы и символы табуляции, а для разделения записей используется символ новой строки. Каждому полю автоматического назначается своя переменная согласно его порядковому номеру следования в записи:

'{print $1}'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George
John
Thomas

GIST | Иногда в качестве разделителей полей удобно использовать не пробелы или символы табуляции, а какой-то особый шаблон. Выше мы упоминали ключ -F, теперь пример, где разделитель полей задаётся с помощью регулярного выражения:

-F '[-,]+' '{print $1 " ("$3-$2")"}'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington (8)
John Adams (4)
Thomas Jefferson (8)

Для более тонкой настройки в «AWK» предусмотрен свой собственный комплект встроенных переменных. Одна из таких переменных с именем RS позволяет задать особый разделитель записей. Подробнее о встроенных переменных чуть позже.

РЕГУЛЯРНЫЕ ВЫРАЖЕНИЯ И БЛОКИ

GIST | «AWK» позволяет использовать регулярные выражения для избирательного выполнения отдельных блоков программы в зависимости от того, совпадает или нет регулярное выражение с текущей записью:

'/on/ { print }'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington, 1789-1797
Thomas Jefferson, 1801-1809

Если не указано действие, то по умолчанию запись выводится в выходной поток и потому в данном случае действие { print } можно было бы вообще не указывать.

ЛОГИЧЕСКИЕ ВЫРАЖЕНИЯ И БЛОКИ

GIST | Есть и другие способов избирательно выполнять блок программы. Мы можем поместить перед блоком программы любое булево выражение для управления исполнением этого блока и «AWK» будет выполнять блок программы только если предыдущее булево выражение истинно:

'$1 == "John"'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
John Adams, 1797-1801

GIST | «AWK» предлагает полный набор операторов сравнения, в том числе обычные ==, <, >, <=, >= и !=. Для сложных выражений стоит помнить об условном операторе if:

'{
  if ($1 == "John" && $2 ~ /Ada/) { 
    print
  }
}'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
John Adams, 1797-1801

Операторы ~ и !~ означают совпадает или не совпадает значение слева с регулярным выражением справа.

НЕОБЯЗАТЕЛЬНЫЕ БЛОКИ BEGIN И END

GIST | Часто встречаются ситуации, когда требуется выполнить код инициализации перед тем, как «AWK» начнет обрабатывать текст. Для таких случаев «AWK» дает возможность определять необязательный блок BEGIN. Это отличное место для инициализации встроенных или глобальных переменных, вывода заголовка и т.д.:

'BEGIN {print "Hello", "World !"}'
any text
Hello World !

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

ВСТРОЕННЫЕ ПЕРЕМЕННЫЕ

«AWK» — динамически типизированный язык: все примитивы хранятся как строки, хотя могут обрабатываться как числа в зависимости от контекста их использования (например, в арифметических выражениях). Основная структура данных языка — ассоциативный массив с индексами-строками.

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

GIST | ARGC — число аргументов командной строки. В данном примере вся логика ограничивается блоком BEGIN, до непосредственной обработки текста дело даже не доходит:

'BEGIN {print "Arguments =", ARGC}' One Two Three Four
any text
Arguments = 5

GIST | ARGV — массив аргументов командной строки. В данном примере массив перебирается с помощью цикла. Циклы используются во множестве языков программировании, поддерживает их и «AWK»:

'BEGIN { 
   for (i = 0; i < ARGC - 1; ++i) { 
      printf "ARGV[%d] = %s\n", i, ARGV[i] 
   } 
}' one two three four
any text
ARGV[0] = mawk
ARGV[1] = one
ARGV[2] = two
ARGV[3] = three

GIST | NF позволяет обращаться к последнему полю данных в записи, не зная его точной позиции. «AWK» автоматически устанавливает значение этой переменной равным числу полей в текущей записи:

-F "[, ]+" '{print $NF ":", $1, $(NF-1)}'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
1789-1797: George Washington
1797-1801: John Adams
1801-1809: Thomas Jefferson

GIST | NR хранит номер текущей записи, начиная с единицы:

'NR < 3'
George Washington, 1789-1797
John Adams, 1797-1801
Thomas Jefferson, 1801-1809
George Washington, 1789-1797
John Adams, 1797-1801

Многие встроенные переменные, например ARGC, устанавливаются автоматически и обычно не предназначены для записи в них, т.е. используются только для чтения.

ПОЛЬЗОВАТЕЛЬСКИЕ ПЕРЕМЕННЫЕ

GIST | Как и любые другие языки программирования, «AWK» позволяет программисту объявлять переменные. Имена переменных могут включать в себя буквы, цифры, символы подчёркивания, но не могут начинаться с цифры. Объявить переменную, присвоить ей значение можно как из командной строки -v var=value так и непосредственно в коде:

-v msg="Hello World !" 
'BEGIN {
   num1="21"
   num2=21
   print num1*2, num2*2, msg + 1
} 
{ print msg }'
a
b
42 42 1
Hello World !
Hello World !

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

АССОЦИАТИВНЫЕ МАССИВЫ

GIST | Из структур данных в «AWK» поддерживаются только ассоциативные массивы. В ассоциативном массиве хранятся пары вида ключ => значение. Предполагается, что ассоциативный массив не может хранить две пары с одинаковыми ключами:

'BEGIN {
   fruits["mango"] = "yellow";
   fruits["orange"] = "orange"
   print fruits["orange"] "\n" fruits["mango"]
}'
anything here
orange
yellow

GIST | Для удаления элемента из массива по его ключу предусмотрено ключевое слово delete:

'BEGIN {
   fruits[1] = "yellow";
   fruits[2] = "orange";
   delete fruits["2"];
   for (fruit in fruits) { 
      print fruits[fruit]
   } 
}'
anything here
yellow

Для перебора всех элементов массива предусмотрена конструкция for...in.

ФУНКЦИИ

GIST | «AWK» имеет набор встроенных функций для выполнения некоторых в известном смысле стандартных операций. К встроенным относятся функции для работы со строками, временем и арифметикой. В следующем примере время конечно же будет отличаться от указанного:

'BEGIN {print strftime("%T",systime()), cos(0), toupper("hello")}'
some text
20:14:27 1 HELLO

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

'# Script execution starts here
BEGIN {
   main(10, 20)
}
# Returns minimum number
function find_min(num1, num2){
   if (num1 < num2)
   return num1
   return num2
}
# Returns maximum number
function find_max(num1, num2){
   if (num1 > num2)
   return num1
   return num2
}
# Main function
function main(num1, num2){
   # Find minimum number
   result = find_min(10, 20)
   print "Minimum =", result

   # Find maximum number
   result = find_max(10, 20)
   print "Maximum =", result
}'
anything here
Minimum = 10
Maximum = 20

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

links

social