AWK AWK СОДЕРЖАНИЕ 1. Язык программирования awk 3 1.1. Структура программы 3 1.2. Лексемы 4 1.2.1. Числовые константы 4 1.2.2. Текстовые константы 5 1.2.3. Ключевые слова 5 1.2.4. Идентификаторы 5 1.2.5. Знаки операций 5 1.2.6. Лексемы для работы с записями и полями 7 1.2.6.1. Разделитель записей 8 1.2.6.2. Разделитель полей 8 1.2.6.3. Записи, состоящие из нескольких строк 8 1.2.6.4. Выходные разделители записей и полей 8 1.2.7. Комментарии 9 1.2.8. Лексемы, используемые для группировки 9 1.3. Первичные выражения 9 1.3.1. Числовые константы 9 1.3.2. Текстовые константы 10 1.3.3. Переменные 10 1.3.4. Функции 11 1.4. Термы 13 1.4.1. Бинарные термы 13 1.4.2. Унарные термы 13 1.4.3. Переменные с приращением 14 1.4.4. Термы, заключенные в скобки 14 1.5. Выражения 14 1.5.1. Конкатенация термов 14 1.5.2. Выражения-присваивания 14 2. Применение awk'а 16 3. Ввод/вывод 17 3.1. Запуск программ на выполнение 17 3.2. Ввод: записи и поля 18 3.3. Ввод из файла 18 3.4. Ввод из командной строки 20 3.5. Вывод на печать 21 3.6. Вывод в различные файлы 25 3.7. Вывод в каналы 25 4. Шаблоны 27 4.1. BEGIN и END 27 4.2. Выражения сравнения 28 4.3. Регулярные выражения 29 4.4. Комбинации шаблонов 31 4.5. Шаблоны-диапазоны 32 5. Действия 33 5.1. Переменные, выражения и присваивания 33 5.2. Инициализация переменных 34 5.3. Переменные-поля 34 5.4. Конкатенация цепочек 35 5.5. Специальные переменные 36 5.6. Типы данных 36 5.7. Массивы 37 6. Специфические возможности 39 6.1. Встроенные функции 39 6.2. Управляющие конструкции 41 6.3. Генерация отчетов 44 6.4. Взаимодействие с shell'ом 45 6.5. Многомерные массивы 46 1. ЯЗЫК ПРОГРАММИРОВАНИЯ AWK awk(1) - это язык программирования, предназначенный для обра- ботки файлов. Цель его разработки - облегчить постановку и ре- шение многих задач, связанных с переработкой текстовой информа- ции. awk выполняет следующие действия: Генерирует отчеты. Сопоставляет шаблоны. Оценивает корректность данных. Фильтрует передаваемые данные. В первой части главы в общих чертах излагается синтаксис awk'а. Затем, начиная с раздела ПРИМЕНЕНИЕ AWK'А, приводится несколько примеров, демонстрирующих использование предоставляемых языком возможностей. 1.1. Структура программы awk-программа - это последовательность операторов следующего вида: |шаблон { действие } |шаблон { действие } | . . . awk обрабатывает набор входных файлов. Основной операцией awk'а является просмотр входных строк в порядке их поступления. Каж- дую строку awk пытается сопоставить с описанным в awk-программе шаблоном. Если сопоставление оказалось успешным, выполняется соответствующее этому шаблону действие. Таким же образом к дан- ной входной строке применяются и все остальные содержащиеся в awk-программе операторы. После того, как проверены все шаблоны, awk переходит к следующей входной строке, причем awk-программа снова выполняется с начала. В awk-операторах могут отсутствовать как шаблоны, так и дейст- вия, но не обе части одновременно. Отсутствие действия эквива- лентно предписанию распечатать строку. Если указано только дей- ствие, оно выполняется для каждой входной строки. Пустая awk-п- рограмма не делает ничего. Поскольку оба компонента оператора являются необязательными, действия должны заключаться в фигур- ные скобки, чтобы отличить их от шаблонов. Например, awk-программа |/x/ { print } выводит все входные строки, содержащие символ x. awk-программа имеет следующую структуру: секция BEGIN основная секция (секция записей) секция END Секция BEGIN выполняется перед обработкой входных строк, секция END - после того, как все исходные данные обработаны. Основная секция выполняется для каждой входной строки. Ключевые слова BEGIN и END являются на самом деле шаблонами специального вида, которые распознаются awk'ом. 1.2. Лексемы Все awk-программы составляются из лексем следующих восьми ви- дов: 1. Числовые константы. 2. Текстовые константы. 3. Ключевые слова. 4. Идентификаторы. 5. Знаки операций. 6. Лексемы для работы с записями и полями. 7. Комментарии. 8. Лексемы, используемые для группировки. 1.2.1. Числовые константы К числовым относятся десятичные константы и константы с плаваю- щей точкой. Десятичная константа - это непустая последователь- ность цифр, которая может содержать десятичную точку (не более чем одну); например: 12, 12., 1.2, .12. Константа с плавающей точкой состоит из десятичной константы, за которой следуют сим- волы e (либо E), необязательный знак + или - и непустая после- довательность цифр; например: 12e3, 1.2e3, 1.2e-3, 1.2E+3. Мак- симально допустимые размер и точность числовых констант машин- но-зависимы. 1.2.2. Текстовые константы Текстовая константа - это цепочка из нуля или большего числа символов, заключенная в двойные кавычки; например: ",", "a", "ab", "12". Чтобы включить в цепочку сам символ двойной кавыч- ки, перед ним надо поставить знак \: "He said, \"Sit!\"". Чтобы включить в цепочку символ перевода строки, в соответствующем месте надо указать \n. Никаких других управляющих последова- тельностей не требуется. Текстовые константы могут иметь (прак- тически) любую длину. 1.2.3. Ключевые слова Следующая таблица содержит используемые в awk'е ключевые слова: |BEGIN break log |END close next |FILENAME continue number |FS exit print |NF exp printf |NR for split |OFS getline sprintf |ORS if sqrt |OFMT in string |RS index substr | int while | length 1.2.4. Идентификаторы Идентификаторы в языке awk служат для того, чтобы обозначать переменные и массивы. Идентификатор - это последовательность букв, цифр и подчеркиваний, начинающаяся с буквы или подчерки- вания. Прописные и строчные буквы различаются. 1.2.5. Знаки операций awk предоставляет арифметические, логические операции, операции сравнения и присваивания, аналогичные соответствующим операциям языка программирования C, а также операции сопоставления с шаб- лонами, заданными регулярными выражениями, аналогичные тем, ко- торые используются в egrep(1). Следующие таблицы содержат описание операций языка awk: Операции присваивания: | +--------+--------------------------+--------------------+ | | знак | использование | описание | | |операции| | | | +--------+--------------------------+--------------------| | | = | присвоить | | | | | | | | | += | сложить и присвоить | X += Y аналогично | | | | | X = X+Y | | | | | | | | -= | вычесть и присвоить | X -= Y аналогично | | | | | X = X-Y | | | | | | | | *= | умножить и присвоить | X *= Y аналогично | | | | | X = X*Y | | | | | | | | /= | разделить и присвоить | X /= Y аналогично | | | | | X = X/Y | | | | | | | | %= | вычислить остаток и | X %= Y аналогично | | | | присвоить | X = X%Y | | | | | | | | ++ | префиксное и постфиксное | ++X и X++ | | | | увеличение | аналогично X = X+1 | | | | | | | | -- | префиксное и постфиксное | --X и Y-- | | | | уменьшение | аналогично X = X-1 | +--------+--------------------------+--------------------+ Арифметические операции: | +--------+--------------------------+ | | знак | описание | | |операции| | | +--------+--------------------------| | | + | унарный и бинарный плюс | | | - | унарный и бинарный минус | | | * | произведение | | | / | частное | | | % | остаток от деления | | | (...) | группировка | | +--------+--------------------------+ Операции сравнения: | +--------+--------------------------+ | | знак | описание | | |операции| | | +--------+--------------------------| | | < | меньше | | | <= | меньше или равно | | | == | равно | | | != | не равно | | | >= | больше или равно | | | > | больше | Логические операции: | +--------+--------------------------+ | | знак | описание | |операции| | +--------+-------------------------- | | && | и | | || | или | | ! | отрицание | | +--------+--------------------------+ Операции сопоставления: | +--------+--------------------------+ | | знак | описание | |операции| | +--------+-------------------------- | | ~ | сопоставляется | | !~ | не сопоставляется | +--------+--------------------------+ 1.2.6. Лексемы для работы с записями и полями $0 - это специальная переменная, чье значение совпадает со зна- чением текущей входной записи. $1, $2 и т.д. - специальные пе- ременные, чьи значения совпадают со значениями соответственно первого, второго и т.д. полей текущей входной записи. Ключевое слово NF (Number of Fields) обозначает специальную переменную, значение которой равно числу полей в текущей входной записи. Таким образом, значение $NF совпадает со значением последнего поля в текущей записи. Отметим, что нумерация полей в записи начинается с 1, а число полей может изменяться от записи к за- писи. В действиях, относящихся к шаблонам BEGIN и END, исполь- зование перечисленных лексем не имеет смысла, поскольку для этих действий не определена текущая входная запись. Ключевое слово NR (Number of Records) обозначает специальную переменную, значение которой равно числу входных записей, про- читанных к данному моменту. Первая прочитанная входная запись имеет номер 1. 1.2.6.1. Разделитель записей Ключевое слово RS (Record Separator) - это специальная перемен- ная, значение которой равно текущему разделителю записей. Пер- воначально значение RS устанавливается равным символу перевода строки; это означает, что соседние входные записи отделяются одна от другой переводами строк. Значение переменной RS можно заменить на любой символ c, выполнив в действии оператор прис ваивания RS = "c". 1.2.6.2. Разделитель полей Ключевое слово FS (Field Separator) - это специальная перемен- ная, значение которой равно текущему разделителю полей. Перво- начальное значение FS - пробел; это означает, что поля отделя ются произвольными непустыми последовательностями пробелов и табуляций. Значение переменной FS можно заменить на любой оди- ночный символ c, выполнив в действии оператор присваивания FS = "c" или указав в командной строке необязательный аргумент -Fc. Два значения c имеют особый смысл. Присваивание FS = " " делает разделителями полей пробелы и табуляции, а аргумент командной строки F\t - табуляции. Если в качестве разделителя полей используется символ, отличный от пробела, считается, что с каждой стороны от разделителя име- ется по полю. Например, если разделитель полей равен 1, то за- пись 1XXX1 состоит из трех полей. Первое и третье - пустые. Ес ли разделитель полей - пробел, поля отделяются друг от друга пробелами и табуляциями и ни одно из NF полей не может быть пустым. 1.2.6.3. Записи, состоящие из нескольких строк Присваивание RS = " " делает разделителем записей пустую стро- ку, а разделителем полей - непустую последовательность из про- белов, табуляций и, возможно, переводов строк. Такое определе- ние не допускает, чтобы первое или последнее поля в записи были пустыми. 1.2.6.4. Выходные разделители записей и полей Значение переменной OFS (Output Field Separator) задает выход- ной разделитель полей; действие print помещает его между поля- ми. После каждой записи print помещает символ, являющийся зна- чением переменной ORS (Output Record Separator). Первоначально ORS устанавливается равным переводу строки, а OFS - пробелу. Данные значения можно заменить любыми другими цепочками при по- мощи присваиваний, например, ORS = "abc" и OFS = "xyz". 1.2.7. Комментарии Комментарий открывается символом # и заканчивается переводом строки. Пример: |# This line is a comment Комментарий можно добавить в конце любой строки awk-программы. 1.2.8. Лексемы, используемые для группировки Обычно в awk'е лексемы отделяются друг от друга непустыми пос- ледовательностями пробелов, табуляций и переводов строк, а так- же другими пунктуационными символами, такими как запятые и точ- ки с запятой. В фигурные скобки, {...}, заключаются действия, в наклонные черты, /.../, - шаблоны, заданные регулярными выраже- ниями, в двойные кавычки, "...", - текстовые константы. 1.3. Первичные выражения В awk'е шаблоны и действия составляются из выражений. Первичные выражения - это основные "строительные блоки" для выражений; к первичным выражениям относятся: |числовые константы |текстовые константы |переменные |функции Каждое выражение имеет как числовое, так и текстовое значение; одно из них обычно является предпочтительным. Правила, по кото- рым определяется предпочтительное значение выражения, излагают- ся ниже. 1.3.1. Числовые константы Формат числовых констант описан ранее, в разделе Лексемы. Зна чения таких констант хранятся в виде вещественных чисел. Текс товое значение числовой константы определяется естественным об- разом. Предпочтительным является числовое значение. Следующая таблица содержит примеры значений числовых констант: | +-----------+----------+-----------+ | | Числовая | Числовое | Текстовое | | | константа | значение | значение | | +-----------+----------+-----------| | | 0 | 0 | 0 | | | 1 | 1 | 1 | | | .5 | 0.5 | .5 | | | .5e2 | 50 | 50 | | +-----------+----------+-----------+ 1.3.2. Текстовые константы Формат текстовых констант описан ранее, в разделе Лексемы. Чис ловое значение текстовой константы равно 0, если только цепоч ка, заключенная в двойные кавычки, не является записью числовой константы. В этом случае числовое значение определяется естест- венным образом. Предпочтительным является текстовое значение, которое всегда совпадает с самой константой. Следующая таблица содержит примеры значений текстовых констант: | +-----------+----------+-----------+ | | Текстовая | Числовое | Текстовое | | | константа | значение | значение | | +-----------+----------+-----------| | | "" | 0 | пусто | | | "a" | 0 | a | | "XYZ" | 0 | XYZ | | "o" | 0 | 0 | | "1" | 1 | 1 | | ".5" | 0.5 | .5 | | ".5e2" | 50 | .5e2 | +-----------+----------+-----------+ 1.3.3. Переменные Переменная - это одна из следующих конструкций: |идентификатор |идентификатор [ выражение ] |$терм Числовое значение любой неинициализированной переменной равно 0, а текстовое значение - пустой цепочке. Идентификатор, указанный сам по себе, - это простая переменная. Переменная вида идентификатор[выражение] представляет элемент ассоциативного массива, названного при помощи идентификатора. Текстовое значение выражения используется в качестве индекса в массиве. Предпочтительное значение переменных идентификатор и идентификатор[выражение] определяется, исходя из контекста. Переменная $0 обозначает текущую входную запись. Ее текстовое и числовое значения совпадают со значениями текущей записи. Если текущая входная запись представляет число, то числовое значение $0 равно данному числу, а текстовое значение - соответствующей цепочке символов. Предпочтительным значением $0 является текс- товое, если только текущая входная запись не представляет чис- ло. $0 нельзя изменить при помощи присваивания. Переменные $1, $2, ... обозначают первое, второе и т.д. поля. Текстовое и числовое значения $i (1 <= i <= NF) совпадают со значениями i-го поля текущей записи. Так же, как и для $0, если i-е поле представляет число, то числовое значение $i равно дан- ному числу, а текстовое значение - соответствующей цепочке сим- волов. Предпочтительным значением $i является текстовое, если только это поле не представляет число. $i можно изменить при помощи присваивания; соответственно изменяется и значение $0. В общем случае, $терм обозначает входную запись, если терм име- ет числовое значение 0, и i-е поле, если целая часть числового значения терма равна i. Если NF < i <= 100, $i ведет себя так же, как неинициализированная переменная. Манипуляции с $i при i > NF не изменяют значения NF. 1.3.4. Функции В awk'е имеется ряд встроенных функций, реализующих часто ис- пользуемые арифметические операции и операции над цепочками символов. Ниже перечислены арифметические операции: |exp (выражение) |int (выражение) |log (выражение) |sqrt (выражение) Арифметические функции (exp, int, log, sqrt) вычисляют, соот- ветственно, экспоненту, целую часть, натуральный логарифм и квадратный корень числового значения выражения. (выражение) мо жет быть опущено, в таком случае функция применяется к $0. Предпочтительным считается числовое значение арифметической функции. Операции над цепочками символов: |getline |index (выражение1, выражение2) |length |length (выражение) |split (выражение, идентификатор, выражение2) |split (выражение, идентификатор) |sprintf (формат, выражение1, выражение2 ...) |substr (выражение1, выражение2) |substr (выражение1, выражение2, выражение3) Выполнение функции getline приводит к тому, что текущая входная запись заменяется на следующую входную запись. Функция возвра- щает 1, если следующая входная запись существует, и 0, если ее нет. Значение переменной NR обновляется. Функция index (e1, e2) по текстовым значениям выражений e1 и e2 находит первое вхождение цепочки e2 в e1 и возвращает номер на- чальной позиции. Если e2 не входит в e1, функция index возвра щает 0. Пример: |index ("abc", "bc") = 2 |index ("abc", "ac") = 0 Функция length без аргументов возвращает число символов в теку- щей входной записи. Если указан аргумент-выражение, length (e) возвращает число символов в текстовом значении e. Пример: ("abc") = 3 |length (17) = 2 Функция split (e, array, sep) разбивает текстовое значение вы- ражения e на поля, которые помещаются затем в array[1], arra- y[2], ... array[n]; в качестве разделителя полей используется текстовое значение аргумента sep. Результат, возвращаемый функ- цией, равен числу обнаруженных полей. Если третий аргумент опу- щен, функция split в качестве разделителя полей использует те кущее значение FS. Например, после обращения |n = split ($0, a) a[1], a[2], ... a[n] - это та же самая последовательность, что и $1, $2, ... $NF. Функция sprintf (f, e1, e2, ...) преобразует текстовые значения выражений e1, e2, ... в соответствии с форматом, специфициро- ванным текстовым значением выражения f. Соглашения об управле- нии форматом такие же, как и для функции printf(3S) в языке программирования C (исключение: не допускается использование символа * для обозначения ширины поля или точности). Функция substr (string, pos) возвращает окончание цепочки сим- волов string, начиная с позиции pos. Функция substr (string, pos, length) возвращает подцепочку аргумента string, начинающу- юся с позиции pos и имеющую длину length. Если длина pos+length больше, чем длина аргумента string, то оба варианта substr эк вивалентны. Пример: |substr ("abc", 2, 1) = "b" |substr ("abc", 2, 2) = "bc" |substr ("abc", 2, 3) = "bc" Значения аргумента pos, меньшие 1, принимаются равными 1. Отри- цательное или нулевое значение аргумента length приводит к пус- тому результату. Предпочтительным для функций sprintf и substr является текстовое значение. Предпочтительное значение осталь- ных функций - числовое. 1.4. Термы Различные арифметические операции, применяемые к первичным вы ражениям, образуют более крупные синтаксические элементы, назы- ваемые термами. Все арифметические вычисления выполняются над вещественными числами. Терм имеет одну из следующих форм: |первичное_выражение |терм биноп терм |уноп терм |переменная_с_приращением |( терм ) 1.4.1. Бинарные термы В терме вида |терм биноп терм биноп может быть одной из пяти арифметических операций +, -, * (умножение), / (деление), % (остаток). Бинарная операция приме- няется к числовым значениям операндов терм1 и терм2, результат также имеет соответствующее числовое значение, которое является предпочтительным, но его можно интерпретировать и как текстовое (см. пункт Числовые константы). Операции *, / и % имеют более высокий приоритет, чем + и -. Все операции левоассоциативны. 1.4.2. Унарные термы В терме вида |уноп терм уноп может быть унарным + или -. Унарная операция применяется к числовому значению операнда терм, результат имеет соответствую- щее числовое значение, которое является предпочтительным, одна- ко его можно интерпретировать и как текстовое. Унарные операции + и - имеют более высокий приоритет, чем *, / и %. 1.4.3. Переменные с приращением Переменная с приращением имеет одну из следующих форм: |++пер |--пер |пер++ |пер-- Терм ++пер имеет значение пер+1, а по своему действию эквива- лентен присваиванию пер = пер+1. Аналогично, терм --пер имеет значение пер-1; он эквивалентен присваиванию пер = пер-1. Да- лее, терм пер++ имеет значение пер и эффект присваивания пер = пер+1, а терм пер-- имеет значение пер и эффект присваивания пер = пер-1. Предпочтительным значением переменной с приращени- ем является числовое. 1.4.4. Термы, заключенные в скобки Для группировки термов используются скобки. 1.5. Выражения Выражения в awk'е имеют одну из следующих форм: |терм |терм терм ... |пер присвоп выражение 1.5.1. Конкатенация термов В выражении вида терм терм ... текстовые значения термов конка- тенируются. Предпочтительным значением результата считается текстовое. Конкатенация термов имеет меньший приоритет, чем би нарные + и -. Например, выражение |1+2 3+4 имеет текстовое (и числовое) значение 37. 1.5.2. Выражения-присваивания Выражение-присваивание имеет следующий вид: |пер присвоп выражение где присвоп - одна из шести операций присваивания: |= |+= |-= |*= |/= |%= Предпочтительным считается то же значение пер, что и предпочти тельное значение выражения. В результате присваивания вида |пер = выражение числовое и текстовое значение пер становятся равными соответст- вующим значениям выражения. Присваивание |пер оп= выражение эквивалентно присваиванию |пер = пер оп выражение где оп - одна из операций +, -, *, /, %. Операции присваивания правоассоциативны и имеют меньший приоритет, чем любая другая операция. Так, выражение a += b *= c-2 эквивалентно последова- тельности присваиваний |b = b * (c-2) |a = a + b 2. ПРИМЕНЕНИЕ AWK'А Задача оставшейся части данной главы - показать синтаксические конструкции awk'а в действии. Разделы соответствуют следующим рассматриваемым вопросам: Ввод/вывод. Шаблоны. Действия. Специфические возможности awk'а. 3. ВВОД/ВЫВОД 3.1. Запуск программ на выполнение Есть два способа запустить на выполнение awk-программу, состоя- щую из операторов вида шаблон {действие}: 1. Если программа короткая (одна-две строки), зачастую проще всего указать программу в качестве первого аргумента командной строки: |awk 'программа' [имя_файла...] где программа - это awk-программа, которую надо выполнить, а имя-файла... - необязательный аргумент, определяющий входной файл (файлы). Отметим, что одинарные кавычки, в которые заклю- чен текст программы, нужны для того, чтобы shell воспринял всю эту цепочку (программу) как первый аргумент команды awk. Напри- мер, чтобы выполнить над файлом file1 awk-программу |/x/ { print } надо передать shell'у команду |awk '/x/ { print }' file1 Если входной файл не специфицирован, awk ожидает исходные дан- ные со стандартного ввода, stdin. Кроме того, можно специфици- ровать, что stdin будет одним из входных файлов; для этого в командной строке указывается символ -. Командная строка |awk 'программа' файл1 - вызывает сначала обработку файла1, а затем - стандартного вво- да. 2. Если, напротив, программа длинная, или если Вы хотите сохра- нить ее для того, чтобы использовать впоследствии, удобно по- местить программу в отдельный файл, и указать awk'у имя файла, из которого ее надо брать. Это можно сделать, использовав в ко мандной строке опцию -f: |awk -f имя_программы [имя_файла ...] Здесь имя_программы указывает файл, содержащий awk-программу, а имя_файла ... - необязательный аргумент, определяющий входной файл (файлы), среди которых, как сказано выше, может быть стан- дартный ввод. Проиллюстрируем альтернативные способы запуска на обработку awk-программ. Выполнение shell'ом командной строки |awk 'BEGIN { print "hello, world"; exit }' приводит к выдаче на стандартный вывод |hello, world Эту же awk-программу можно выполнить, поместив ее текст |BEGIN { print "hello, world"; exit } в файл awkprog, а затем набрав в shell'е команду |awk -f awkprog Результат будет тот же. 3.2. Ввод: записи и поля awk читает входные записи по одной. Если разделитель записей не переустановлен, запись - это последовательность символов, начи- ная с той, на которой остановился ввод, и до символа перевода строки либо до конца файла. После того, как цепочка символов считана, она присваивается переменной $0. Прочитав запись, awk разбивает ее на поля, из которых она сос- тавлена. Если разделитель полей не переустановлен, полем явля- ется цепочка символов, отделенная пробелами или табуляциями. 3.3. Ввод из файла В качестве примера рассмотрим файл countries. Данный файл сос- тоит из строк, содержащих площадь (в тысячах квадратных миль), население (в миллионах человек) и континент для десяти крупней- ших по площади стран мира. (Данные взяты на 1978 год; Россия отнесена к Азии.) |Russia 8650 262 Asia |Canada 3852 24 North America |China 3692 866 Asia |USA 3615 219 North America |Brazil 3286 116 South America |Australia 2968 14 Australia |India 1269 637 Asia |Argentina 1072 26 South America |Sudan 968 19 Africa |Algeria 920 18 Africa Широкие промежутки между колонками при первоначальном вводе за- даны табуляциями; слова "North" ("South") и "America" отделяет ся одиночным пробелом. Данный файл будет использоваться в этой главе в качестве исходного во многих awk-программах, он типичен для того рода информации, для обработки которой лучше всего приспособлен awk (смесь слов и чисел, организованных в колонки или поля, разделенные пробелами либо табуляциями). Каждая из строк файла countries состоит из четырех или пяти слов, если считать, что поля разделяются пробелами и/или табу ляциями, как и подразумевается в awk'е по умолчанию, если не задано противное. В приведенном примере первой записью являет- ся |Russia 8650 262 Asia После того, как эта запись прочитана awk'ом, она присваивается переменной $0. Если требуется сослаться на всю запись целиком, это делается при помощи $0. Например, действие |{ print $0 } распечатывает всю запись. Поля, принадлежащие записи, присваиваются переменным $1, $2, $3 и т.д.; это означает, что в awk-программе для обращения к пер вому полю текущей записи используется переменная $1, для обра- щения ко второму полю - переменная $2, для обращения ко i-ому полю - переменная $i. Так, в приведенном выше примере (файл countries) для первой записи |$1 эквивалентно цепочке "Russia" |$2 эквивалентно цепочке "8650" |$3 эквивалентно цепочке "262" |$4 эквивалентно цепочке "Asia" |$5 эквивалентно пустой цепочке | . . . Чтобы напечатать континент, затем название страны, и наконец, численность населения, можно использовать следующую команду: |awk '{ print $4, $1, $3 }' countries Можно заметить, что она прордит не совсем тот вывод, который требуется, поскольку по умолчанию разделителем полей считается не только табуляция, но и пробел. Неудобство состоит в том, что South America и North America содержат пробел. Поэтому правиль- нее будет использовать команду |awk -F\t '{ print $4, $1, $3 }' countries 3.4. Ввод из командной строки Ранее, в разделе Запуск программы на выполнение, уже говори- лось, что для того, чтобы задать awk-программу, которую требу- ется выполнить, можно либо вставить ее, заключив в одинарные кавычки, в командую строку, либо поместить в файл, а в команд- ной строке указать имя этого файла, поместив перед ним флаг -f. Кроме того, в командной строке можно устанавливать значения пе- ременных. В awk'е значения переменным можно присваивать внутри awk-прог- раммы. Поскольку типы переменных не описываются, переменная создается просто посредством обращения к ней. Пример присваива- ния переменной нового значения: |x=5 Данный оператор awk-программы присваивает переменной x значение 5. Такой вид присваивания можно задать и в командной строке - это еще один способ указать исходные данные для awk-программы. Например, команда |awk '{ print x }' x=5 - будет распечатывать значение 5 после прочтения очередной запи- си. Знак - в конце командной строки необходим для того, чтобы указать, что исходные данные надо брать со стандартного ввода, а не из файла с именем x=5. После ввода команды пользователь должен ввести исходные данные, завершив их сомволом CTRL+D. Если исходные данные берутся из файла (например, с именем fi- le1), команда должна выглядеть так: |awk '{ print x }' x=5 file1 Если необходимо изменить разделитель полей или разделитель за- писей, это также можно сделать в командной строке, как в сле дующем примере: |awk -f awkprog RS=":" file1 В данном примере разделитель записей устанавливается равным символу :. В результате программа, содержащаяся в файле awkp rog, обрабатывает записи, разделенные не символами перевода строки, а двоеточиями; исходные данные берутся из файла file1. Подобным же образом в командной строке можно изменить раздели- тель полей. Предусмотрена специальная опция, -Fx; если она указана, значе ние разделителя полей изменяется с пробела или табуляции на символ x. Например, командная строка |awk -F: -f awkprog file1 заменяет разделитель полей на символ :. Отметим, что если раз делитель полей явно установлен равным табуляции (то есть при помощи опции -F или посредством присваивания переменной FS), пробелы не считаются разделителями полей. Однако обратное не верно: даже если разделитель полей явно установлен равным про- белу, табуляции тем не менее считаются разделителями полей. 3.5. Вывод на печать Действию может не соответствовать шаблон; в таком случае дейст- вие выполняется для всех строк, как следующая простая программа распечатки: |{ print } Это одно из простейших действий, выполняемых awk'ом. Оно выдает каждую строку на печать. Полезнее бывает печатать не всю стро- ку, а одно или несколько полей. Например, при использовании файла countries, описанного ранее, командная строка |awk '{ print $1, $3 }' countries распечатывает название и население стран: |Russia 262 |Canada 24 |China 866 |USA 219 |Brazil 116 |Australia 14 |India 637 |Argentina 26 |Sudan 19 |Algeria 18 Точка с запятой в конце списка операторов необязательна. awk допускает как действие |{ print $1 } так и |{ print $1; } Эти два действия эквивалентны. Однако, если требуется поместить два awk-оператора в одну строку awk-текста, точка с запятой не- обходима. Например, число 5 можно напечатать так: |{ x=5; print x } Скобки для оператора print также необязательны. Действие |{ print $3, $2 } эквивалентно |{ print ($3, $2) } Аргументы оператора print, разделенные запятыми, при печати разделяются текущим выходным разделителем полей (обычно пробе- лом, даже если входные поля разделяются табуляциями). OFS - это еще одна специальная переменная, которую может переустановить программист. (Полностью все такие переменные перечисляются ни- же.) print может также выводить на печать цепочки, определенные непосредственно в awk-программах, например |{ print "hello, world " } Как уже было сказано, awk предоставляет несколько специальных переменных, значениями которых бывает удобно управлять, напри мер, FS и RS. В следующем примере вводятся еще две специальные переменные. NR и NF имеют целочисленные значения, равные соот- ветственно номеру текущей прочитанной записи и числу полей в ней. Так, действие |{ print NR, NF, $0 } для каждой записи выводит на печать ее номер, число полей в ней, а затем саму запись. Применение этой программы к файлу countries дает следующий результат: |1 4 Russia 8650 262 Asia |2 5 Canada 3852 24 North America |3 4 China 3692 866 Asia |4 5 USA 3615 219 North America |5 5 Brazil 3286 116 South America |6 4 Australia 2968 14 Australia |7 4 India 1269 637 Asia |8 5 Argentina 1072 26 South America |9 4 Sudan 968 19 Africa |10 4 Algeria 920 18 Africa Программа |{ print NR, $1 } выводит на печать |1 Russia |2 Canada |3 China |4 USA |5 Brazil |6 Australia |7 India |8 Argentina |9 Sudan |10 Algeria Этот пример демонстрирует полезный прием: как приписать к эле- ментам списка их последовательные номера. Оператор print без аргументов печатает текущую входную запись. Чтобы напечатать пустую строку, надо использовать |{ print "" } awk предоставляет также оператор printf, позволяющий програм- мисту самостоятельно задавать требуемый формат вывода. Для рас- печатываемых числовых значений print использует по умолчанию формат %.6g. Оператор |printf "формат", выражение, выражение, ... преобразует указанные в списке аргументов выражения в соот- ветствии со спецификацией, задаваемой цепочкой формат, и распе- чатывает их. Оператор printf практически идентичен функции printf(3S) из C-библиотеки. Например, действие |{ printf "%10s %6d %6d\n", $1, $2, $3 } распечатывает $1 как цепочку из 10 символов (выравненную по правому краю). Второе и третье поля (6-значные числа) образуют аккуратную таблицу, состоящую из двух колонок: | Russia 8650 262 | Canada 3852 24 | China 3692 866 | USA 3615 219 | Brazil 3286 116 |Australia 2968 14 | India 1269 637 |Argentina 1072 26 | Sudan 968 19 | Algeria 920 18 Оператор printf не порождает автоматически никаких выходных разделителей полей или переводов строк. Программист должен до- бавить их самостоятельно, как это сделано в данном примере. Можно указывать управляющие символы \n, \t, \b (забой), \r (возврат каретки). Есть еще одна, третья ситуация, в которой может иметь место вы- дача на стандартный вывод. Это происходит, когда в awk-операто- ре не специфицировано действие, а только шаблон. В таком случае распечатывается запись $0 целиком. Например, программа |/x/ распечатывает все записи, содержащие символ x. Вывод на печать сопровождается использованием двух специальных переменных, OFS и ORS. По умолчанию они устанавливаются равны- ми, соответственно, пробелу и символу перевода строки. Перемен- ная OFS выдается на стандартный вывод, когда в списке аргумен- тов оператора print встречается запятая. Например, оператор: |{ x="hello"; y="world" | print x, y |} выводит |hello world Однако, если запятой нет: |{ x="hello"; y="world" | print x y |} результат будет таким: |helloworld Чтобы вывести запятую, можно либо явно указать ее в операторе print: |{ x="hello"; y="world" | print x"," y |} либо в секции BEGIN переопределить OFS: |BEGIN { OFS=", " } |{ x="hello"; y="world" | print x, y |} В обоих случаях результатом будет: |hello, world Отметим, что при печати $0 выходной разделитель полей не ис- пользуется. 3.6. Вывод в различные файлы shell позволяет переназначать стандартный вывод в файл. awk также позволяет направить вывод во многие различные файлы, при- чем это можно сделать внутри awk-программы. Вернемся к входному файлу countries. Допустим, требуется вывести всю информацию о странах из Азии в файл, называемый ASIA, всю информацию о странах из Африки в файл AFRICA, и т.д. Это можно сделать при помощи следующей awk-программы: |{ if ($4 == "Asia") print > "ASIA" | if ($4 == "Europe") print > "EUROPE" | if ($4 == "North") print > "NORTH_AMERICA" | if ($4 == "South") print > "SOUTH_AMERICA" | if ($4 == "Australia") print > "AUSTRALIA" | if ($4 == "Africa") print > "AFRICA" |} Операторы, управляющие последовательностью вычислений, обсужда- ются позднее. В общем случае можно направить вывод в файл, указав его имя после оператора print или printf. В операторе вида |printf > "имя_файла" имя_файла задает файл, в который направляются данные. У опера- тора могут быть указаны произвольные допустимые аргументы. Подчеркнем, что имя файла заключается в кавычки. При отсутствии кавычек имена файлов трактуются как переменные, которые могут оказаться неинициализированными; в таком случае весь вывод нап- равляется в stdout, если он не переназначен в командной строке. Если вместо > указать >>, вывод будет добавлен в конец файла, а не заменит его содержимое. Отметим, что существует ограничение на максимальное число файлов, в которые можно таким образом направить вывод. Сейчас оно равно десяти. 3.7. Вывод в каналы Вывод можно направить не только в обычный файл, но и в канал. Пример: |{ | if ($2 == "XX") print | "mailx mary" |} где mary - имя, под которым пользователь входит в систему. Лю- бая запись со вторым полем, равным XX, отправляется пользовате- лю mary в виде почты. awk ждет, пока не выполнится программа целиком, а затем выполняется команда, соединенная с ней каналом [в данном случае - команда mailx(1)]. Программа |{ | print $1 | "sort" |} выделяет из каждой входной записи первое поле, сортирует эти поля и затем распечатывает их. Еще один пример использования каналов - следующая общеупотреби- мая конструкция, гарантирующая, что весь вывод обязательно бу- дет направлен на Ваш терминал: |{ | print ... | "cat -v > /dev/tty" |} Во всех операторах вывода, включающих переназначение вывода, файлы или каналы идентифицируются по именам, однако создаются или открываются они только один раз за время выполнения прог- раммы. 4. ШАБЛОНЫ Шаблоны указываются перед действиями и играют роль фильтров, определяющих, должны ли действия выполняться. В качестве шабло- нов используется множество различных конструкций: некоторые ключевые слова арифметические выражения и выражения сравнения регулярные выражения комбинации перечисленных выражений 4.1. BEGIN и END Ключевое слово BEGIN является специальным шаблоном, сопоставля- ющимся с началом исходных данных перед тем, как считывается первая запись. Ключевое слово END - специальный шаблон, сопос- тавляющийся с концом исходных данных после того, как обработана последняя строка. Таким образом, BEGIN и END дают возможность перехватить управление до и после обработки исходных данных, чтобы выполнить инициализационные и заключительные действия. В следующем примере шаблон BEGIN используется для того, чтобы вывести заголовки столбцов. Программа |BEGIN {print "Country","Area","Population","Continent"} | { print } порождает такой текст: |Country Area Population Continent |Russia 8650 262 Asia |Canada 3852 24 North America |China 3692 866 Asia |USA 3615 219 North America |Brazil 3286 116 South America |Australia 2968 14 Australia |India 1269 637 Asia |Argentina 1072 26 South America |Sudan 968 19 Africa |Algeria 920 18 Africa Формат, в котором выводятся заголовки, не очень хорош; в случа- ях, когда важно качество внешнего представления, обычно исполь- зуется printf - он больше подходит для такой задачи. Напомним также, что секцию BEGIN удобно использовать для пере- установки специальных переменных, в частности FS и RS. Пример: |BEGIN { FS= "\t" |printf "Country\t\t Area\tPopulation\tContinent\n\n"} |{ printf "%-10s\t%6d\t%6d\t\t% -14s\n", $1, $2, $3, $4 } |END { print "The number of records is", NR } В этой программе FS устанавливается в секции BEGIN равным табу- ляции, в результате чего все записи в файле countries содержат ровно четыре поля. Рекомендуется указывать BEGIN первым в пос- ледовательности шаблонов, а END - последним. 4.2. Выражения сравнения Произвольные выражения, включающие сравнения цепочек символов или чисел, являются допустимыми шаблонами awk'а. Например, если требуется распечатать информацию только о странах, население которых превышает 100 миллионов, можно использовать шаблон |$3 > 100 Простая awk-программа, состоящая из одного этого шаблона без всякого действия, распечатает только те записи, значение треть- его поля в которых больше 100: |Russia 8650 262 Asia |China 3692 866 Asia |USA 3615 219 North America |Brazil 3286 116 South America |India 1269 637 Asia Чтобы напечатать названия стран, расположенных в Азии, надо набрать программу |$4 == "Asia" { print $1 } Ее результатом будет |Russia |China |India Проверяемые условия задаются знаками операций <, <=, ==, !=, >=, >. Если в выражении сравнения оба операнда являются числа- ми, выполняется числовое сравнение; в противном случае операнды сравниваются как цепочки символов. Так, шаблон |$1 >= "S" отбирает строки, начинающиеся с S, T, U и т.д.; в нашем случае это |USA 3615 219 Nor |Sudan 968 19 Africa Если дополнительная информация о типе отсутствует, поля тракту- ются как цепочки символов, поэтому программа |$1 == $4 сравнивает первое и четвертое поля как цепочки символов и выво- дит на печать единственную строку: |Australia 2968 14 Australia 4.3. Регулярные выражения Кроме возможностей, проиллюстрированных в предыдущем разделе, в awk'е есть и более мощное средство спецификации шаблонов - ре- гулярные выражения. Простейшим регулярным выражением является буквальное указание требуемой цепочки. Пример (регулярные выра- жения обрамляются символами /): |/Asia/ Данное регулярное выражение - законченная awk-программа, печа- тающая все строки, содержащие какое-либо вхождение цепочки Asia. Если строка содержит более длинное слово, частью которого является цепочка Asia, например Asiatic, она все равно печата- ется (в файле countries, однако, таких слов нет). Используемые в awk'е регулярные выражения трактуются так же, как в распознавателе шаблонов egrep(1). Ряд символов имеет спе- циальный смысл. Например, чтобы напечатать все строки, начинающиеся с A, следу- ет указать регулярное выражение |/^A/ Для печати всех строк, начинающихся с A, B или C, годится регу- лярное выражение |/^[ABC]/ Все строки, оканчивающиеся цепочкой ia, распечатываются с по- мощью шаблона |/ia$/ В общем случае, символ ^ обозначает начало строки, символ $ - конец строки, а символы, заключенные в квадратные скобки, [], сопоставляются с любым одиночным символом из перечисленных в скобках. Кроме того, awk позволяет использовать круглые скобки для группировки, а символ | - для перечисления альтернатив. Знак + определяет, что предшествовавшее ему выражение должно сопоставляться один или более раз, знак ? обозначает повторение нуль или один раз. Программа |/x|y/ { print } печатает все цепочки, содержащие x или y, программа |/ax+b/ { print } выводит все цепочки, содержащие символ a, за которым следует один или более символов x, а затем b (например, axb, Paxxxxxxxb, QaxxbR). Программа |/ax?b/ { print } печатает все цепочки, содержащие символ a, за которым следует необязательный x, а затем символ b (например, ab, axb, yaxbPPPP, CabD). Два символа, . и *, имеют тот же смысл, что и в ed(1). Более подробно, . обозначает произвольный символ, а * - нуль или бо- лее вхождений предыдущего символа. Так, шаблон |/a.b/ сопоставляется с любой записью, содержащей символ a, за которым следует произвольный символ, а затем символ b. Другими словами, запись должна включать символы a и b, разделенные произвольным символом. Например, /a.b/ сопоставляются с axb, aPb и xxxxaXbxx, но не с ab, axxb. Шаблон |/ax*c/ сопоставляется с записью, содержащей символы a и c, разделенные цепочкой из произвольного, возможно нулевого, числа символов x. Например, она сопоставляется с цепочками |ac |axc |pqraxxxxxxxxxxc901 Так же, как и в ed(1), можно отменить специальную интерпретацию метасимволов (например, ^ и *). Для этого перед такими символа- ми ставится знак \. Например, шаблон |/\/.*\// сопоставляется с произвольной цепочкой символов, заключенной в "скобки" /.../. Можно также специфицировать, что некоторое поле (переменная) должно сопоставляться с регулярным выражением (либо не сопос- тавляться). Это делается при помощи операций ~ и !~. Например, для входного файла countries следующая программа выводит на пе- чать все страны, названия которых заканчиваются цепочкой ia: |Russia |Australia |India |Algeria 4.4. Комбинации шаблонов Шаблон может быть составлен из более простых шаблонов, объеди- ненных операциями || (ИЛИ), && (И), ! (ОТРИЦАНИЕ) и скобок. Например, шаблон |$2 >= 3000 && $3 >= 100 отбирает строки, относящиеся к странам, как площадь, так и на- селение которых достаточно большие: |Russia 8650 262 Asia |China 3692 866 Asia |USA 3615 219 North America |Brazil 3286 116 South America Шаблон |$4 == "Asia" || $4 == "Africa" отбирает строки, четвертое поле в которых совпадает либо с Asia, либо с Africa. Альтернативный способ спецификации послед- него шаблона: |$4 ~ /^(Asia|Africa)$/ Шаблон специфицирует отбор записей, четвертое поле в которых сопоставляется с цепочкой Asia или Africa, причем сопоставление начинается с первого символа и заканчивается последним. Операции && и || гарантирует, что их операнды обрабатываются слева направо; обработка прекращается, как только установлено истинностное значение выражения. 4.5. Шаблоны-диапазоны Шаблон в awk'е может также состоять из двух шаблонов, разделен- ных запятой: |шаблон1, шаблон2 { действие } В этом случае действие выполняется для каждой строки, начиная со строки, удовлетворяющей шаблону1, и заканчивая строкой, удовлетворяющей шаблону2. Следующий оператор, в котором от- сутствует действие, |/Canada/,/Brazil/ печатает все строки, расположенные между строкой, содержащей цепочку Canada, и строкой, содержащей цепочку Brazil. Например: |Canada 3852 24 North America |China 3692 866 Asia |USA 3615 219 North America |Brazil 3286 116 South America Шаблон |NR == 2, NR == 5 { ... } вызывает выполнение действия для входных строк со второй по пя- тую. Разные типы шаблонов можно смешивать, например: |/Canada/, $4 == "Africa" Данный оператор печатает все строки, начиная с той, которая со- держит цепочку Canada, и заканчивая той, четвертое поле которой суть Africa. Примечание Приведенное выше обсуждение механизма сопоставления с шаблоном относится к разделу шаблонов awk-операторов. Сопоставление с шаблоном может также иметь место в опе- раторах if и while в разделе действий. См. раздел Уп- равляющие конструкции. 5. ДЕЙСТВИЯ Действием в языке awk является последовательность операторов, разделенных переводами строки или точками с запятой. Действия позволяют решать множество задач, связанных с бухгалтерскими расчетами и обработкой цепочек символов. 5.1. Переменные, выражения и присваивания awk дает возможность выполнять арифметические вычисления и сох ранять их результаты в переменных для последующего использова- ния. В качестве примера рассмотрим вывод на печать плотности населения тех стран, информация о которых содержится в файле countries: |{ print $1 (1000000 * $3) / ($2 * 1000) } (Напомним, что в этом файле население страны указано в миллио- нах человек, а площадь - в тысячах квадратных миль.) Результа- том является количество людей, приходящееся на одну квадратную милю: |Russia 30.289 |Canada 6.23053 |China 234.561 |USA 60.5809 |Brazil 35.3013 |Australia 4.71698 |India 501.97 |Argentina 24.2537 |Sudan 19.6281 |Algeria 19.5652 Формат у этой таблицы получился не очень красивым; если исполь зовать вместо print оператор printf: |{ printf "%10s %6.1f\n", $1, (1000000*$3)/($2*1000) } результат будет таким: |Russia 30.3 |Canada 6.2 |China 234.6 |USA 60.6 |Brazil 35.3 |Australia 4.7 |India 502.0 |Argentina 24. |Sudan 19.6 |Algeria 19.6 Все промежуточные арифметические вычисления выполняются над ве- щественными числами. Допускаются следующие арифметические опе- рации: +, -, *, /, % (остаток от деления). Чтобы вычислить суммарное население и число стран из Азии, мож- но использовать программу |/Asia/ { pop += $3; ++n } |END { print "total population of", n, | "Asian countries is", pop } которая печатает: |total population of 3 Asian countries is 1765 Операции ++, --, +=, -=, /=, *=, %= используются в awk'е так же, как в языке C. Операция ++, например, увеличивает значение переменной на единицу. Операции ++ и -- (как и в C) могут быть и префиксными, и постфиксными. Все эти операции можно использо- вать также и в выражениях. 5.2. Инициализация переменных В предыдущем примере переменные pop и n не были инициализирова- ны; тем не менее, программа работала нормально. Это происходит потому, что (по умолчанию) переменные инициализируются пустой цепочкой, числовое значение которой равно 0. Данное соглашение устраняет необходимость большинства инициализаций переменных в секции BEGIN. В следующей программе, определяющей страну с самым большим на- селением, также может быть использована неявная инициализация: |maxpop < $3 { | maxpop = $3 | country = $1 | } |END { print country, maxpop } Ее результат: |CHINA 866 5.3. Переменные-поля Поля в awk'е разделяют по существу все свойства переменных. Они используются в арифметических и текстовых операциях, могут быть инициализированы пустой цепочкой, им можно присвоить другое значение. Например, можно разделить значение второго поля на 1000, чтобы получить площадь в миллионах квадратных миль: |{ $2 /= 1000; print } вычислить значение поля, исходя из значений двух других: |BEGIN { FS = "\t" } | { $4 = 1000 * $3 / $2; print } присвоить полю новое значение - цепочку: |/USA/ { $1 = "United States" ; print } Последняя программа заменяет в одной из строк поле USA на United States и печатает новое значение строки: |United States 3615 219 North America К полям можно обращаться посредством выражений; например, $NF обозначает последнее поле, а $(NF-1) - второе от конца. Отме- тим, что скобки в последнем примере необходимы, поскольку $NF-1 есть выражение, значение которого на 1 меньше значения послед- него поля. 5.4. Конкатенация цепочек Чтобы сконкатенировать цепочки символов, надо написать их одну за другой. Например, программа |{ x = "hello" | x = x", world" | print x |} печатает |hello, world При использовании в качестве входного файла countries, програм- ма |/^A/ { s = s " " $1 } |END { print s } напечатает | Australia Argentina Algeria В конкатенациях можно указывать переменные, текстовые и число- вые выражения; числовые значения в данном случае трактуются как текстовые. 5.5. Специальные переменные Некоторые переменные в awk имеют специальное назначение. В этом разделе приводится полный список таких переменных и описание их использования: NR Порядковый номер текущей записи. NF Число полей в текущей записи. FS Входной разделитель полей, по умолчанию равен пробелу/табуляции. RS Входной разделитель записей, по умолчанию ра- вен символу перевода строки. $i i-е поле текущей записи. $0 Текущая входная запись целиком. OFS Выходной разделитель полей, по умолчанию ра вен пробелу. ORS Выходной разделитель записей, по умолчанию равен символу перевода строки. OFMT Формат для вывода на печать чисел, использу- ется оператором print; по умолчанию равен %.6g. FILENAME Имя файла, из которого в данный момент произ- водится ввод. Это удобно, поскольку обычно awk-программы имеют вид |awk -f программа файл1 файл2 файл3 ... 5.6. Типы данных Переменные (и поля) принимают числовые или текстовые значения в зависимости от контекста. Например, в присваивании |pop += $3 pop полагается числом, в то время как в присваивании |country = $1 country - это цепочка символов. В выражении |maxpop < $3 тип maxpop зависит от данных, которые содержатся в $3, что оп- ределяется во время выполнения программы. В общем случае, каждая переменная (поле) является потенциально цепочкой символов или числом, либо одновременно и тем и другим. Если значение переменной устанавливается присваиванием |пер = выражение то ее тип становится равным типу выражения. ("Присваивание" - это также и +=, ++, -= и т.д.) Арифметическое выражение имеет тип число; конкатенация цепочек имеет тип цепочка_символов. В сравнениях, если оба операнда являются числами, они и сравни- ваются как числа. В противном случае, операнды, если требуется, преобразуются в цепочки символов и выполняется сравнение этих цепочек. Следующие трюки позволяют преобразовать тип любого выражения: |выражение + 0 трактуется как число, а |выражение "" - как цепочка символов. (Последнее выражение - это конкатенация с пустой цепочкой.) 5.7. Массивы Кроме обыкновенных переменных, awk предоставляет одномерные массивы. Массивы не описываются явным образом, их элементы воз- никают, когда на них ссылаются. Индекс может быть произвольной непустой цепочкой, в том числе и не имеющей числового значения. Как пример использования обычного числового индекса, оператор |x [NR] = $0 присваивает текущую входную строку NR-му элементу массива x. В принципе, если выполнить следующую awk-программу: | { x [NR] = $0 } |END { ... программа ... } можно в произвольном порядке обрабатывать все исходные данные целиком (хотя, быть может, и довольно медленно). Первая строка этой программы заносит входные записи в массив x. Программа |{ x [NR] = $1 } (в случае обработки файла countries) порождает массив со следу- ющим содержимым: |x [1] = "Russia" |x [2] = "Canada" |x [3] = "China" | . . . Использование в качестве индексов в массиве нечисловых значений придает awk'у возможности, сходные с возможностями ассоциатив- ной памяти Снобол-таблиц. Например, можно написать программу |/Asia/ { pop ["Asia"] += $3 } |/Africa/ { pop ["Africa"] += $3 } |END { print "Asia=" pop ["Asia"] , | "Africa=" pop ["Africa"] } которая порождает результат |Asia=1765 Africa=37 Отметим, что индексные выражения можно конкатенировать. Кроме того, в качестве индекса при обращении к массиву можно исполь- зовать произвольное выражение. Так, в конструкции |area [$1] = $2 текстовое значение первого поля строки используется как индекс в массиве area. 6. СПЕЦИФИЧЕСКИЕ ВОЗМОЖНОСТИ В этом, заключительном разделе описывается использование неко- торых специфических возможностей awk'а. 6.1. Встроенные функции Предоставляемая awk'ом функция length позволяет вычислить длину цепочки символов. Следующая программа печатает длину и содержи- мое каждой записи: |{ print length, $0 } В данном случае length эквивалентно length($0), что обозначает длину текущей записи. В общем случае, length (x) возвращает длину аргумента x, трактуемого как цепочка символов. При использовании countries в качестве входного файла, следую щая программа напечатает самое длинное название страны: |length ($1) > max { max = length ($1); name = $1 } |END { print name } Функция split в форме |split (s, array) присваивает поля цепочки s последовательным элементам массива array. Например, обращение |split ("Now is the time", w) присваивает значение Now элементу w[1], is - w[2], the - w[3], time - w[4]. Все другие элементы массива w[] устанавливаются равными пустой цепочке. Можно указать, что роль разделителя по- лей должен играть не пробел, а другой символ. В таком случае используется иная форма функции split, с тремя аргументами: |n = split (s, array, sep) Данный вызов разбивает цепочку s на поля и помещает их в arra- y[1], ..., array[n]. Результат, возвращаемый split, равен числу обнаруженных полей. Если аргумент sep указан, задаваемая им це- почка используется в качестве разделителя полей; в противном случае используется FS. Это бывает удобно, если требуется в се- редине awk-программы переопределить для одной записи раздели- тель полей. Кроме того, awk предоставляет математические функции |sqrt |log |exp |int Это функции вычисления квадратного корня, натурального логариф- ма, экспоненты и целой части числа. Последняя функция возвраща- ет максимальное целочисленное значение, не превосходящее значе- ния аргумента. Перечисленные функции заимствуются из математи чекой библиотеки языка C (awk-функции int соответствует функция floor библиотеки libm), поэтому в случае ошибки они возвращают такие же результаты, что и их аналоги из libm (см. Справочник программиста). Функция substr в форме |substr (s, m, n) возвращает подцепочку цепочки s, начинающуюся с позиции m и содержащую не более n символов. Если третий аргумент (в данном случае - n) отсутствует, выделяется подцепочка до конца s. Нап- ример, программа |{ $1 = substr ($1, 1, 3); print } позволяет сократить названия стран в файле countries: |Rus 8650 262 Asia |Can 3852 24 North America |Chi 3692 866 Asia |USA 3615 219 North America |Bra 3286 116 South America |Aus 2968 14 Australia |Ind 1269 637 Asia |Arg 1072 26 South America |Sud 968 19 Africa |Alg 920 18 Africa Если s - число, substr использует его текстовое представление: |substr (123456789, 3, 4) = 3456 Функция |index (s1, s2) возвращает номер начальной позиции первого вхождения цепочки s2 в цепочку s1, либо нуль, если цепочка s2 не входит в цепочку s1. Функция sprintf форматирует выражения так же, как это делает оператор printf, однако не отправляет результат на стандартный вывод, а присваивает его некоторой переменной. Например, опера- тор |x = sprintf ("%10s %6d", $1, $2) присваивает переменной x цепочку символов, полученную форматным преобразованием значений $1 и $2, после чего x можно использо- вать в последующих вычислениях. Функция getline немедленно читает следующую входную запись. Значения полей, переменных $0 и NR переустанавливаются, однако управление остается в том же самом месте awk-программы. getline возвращает 0, если обнаружен конец файла, и 1, если считана обычная запись. 6.2. Управляющие конструкции awk позволяет использовать в действиях следующие управляющие конструкции: if-else while for и составной оператор, такой же, как в языке C. Оператор if имеет следующий вид: |if ( условие ) оператор1 else оператор2 Условие вычисляется; если оно истинно, выполняется оператор1; в противном случае выполняется оператор2. Часть else является не- обязательной. Несколько операторов, заключенных в фигурные скобки, трактуются как единый оператор. В примере, приведенном в разделе Инициализация переменных, вычисление максимума насе- ления можно перенести из шаблона в действие, если воспользо- ваться оператором if: |{ if (maxpop < $3) { | maxpop = $3 | country = $1 | } |} |END { print country, maxpop } Оператор while имеет вид: |while ( условие ) оператор Условие вычисляется; если оно истинно, выполняется оператор. Условие вычисляется снова, и если оно истинно, опять выполняет- ся оператор. Цикл повторяется до тех пор, пока условие истинно. Например, следующая программа распечатывает все входные поля, по одному на каждой строке: |{ i = 1 | while (i <= NF) { | print $i | ++i | } |} Другой пример - алгоритм Евклида нахождения наибольшего общего делителя $1 и $2: |{ printf "the greatest common divisor of " | $1 "and ", $2, "is" | while ($1 != $2) { | if ($1 > $2) $1 -= $2 | else $2 -= $1 | } | printf $1 "\n" |} Оператор for, аналогичный соответствующей конструкции языка C, имеет вид |for ( выражение1 ; условие ; выражение2 ) оператор Так, |{ for (i = 1 ; i <= NF; i++) | print $i |} - это еще одна awk-программа, распечатывающая все входные поля, по одному на каждой строке. Имеется альтернативная форма оператора for, удобная для доступа к элементам ассоциативного массива в awk: |for ( i in массив ) оператор Такая конструкция задает выполнение оператора для i, принимаю- щего последовательно каждое значение индекса в массиве. Переби- раются все индексы, однако порядок перебора не определен. Хаос гарантируется, если в теле цикла изменяется переменная i или создаются новые элементы массива. Цикл for в такой форме можно использовать, например, чтобы после завершения основной части программы напечатать все входные записи, предварив их порядко- выми номерами: | { x [NR] = $0 } |END { for (i in x) print i, x [i] } Более содержательным является следующий пример - индексы-цепоч- ки используются для вычисления суммарного населения стран по континентам: |BEGIN { FS="\t" } | { population [$4] += $3 } |END { for (i in population) | print i, population [i] | } В данной программе тело цикла for выполняется для i, равного по очереди различным названиям континентов, до тех пор, пока все возможные значения i не будут исчерпаны (то есть пока все це почки-названия не будут использованы). Отметим, однако, что по рядок вычислений не определен. Например, такая программа может напечатать: |Africa 37 |South America 142 |Asia 1765 |North America 243 |Australia 14 Отметим, что условие в операторах if, while и for может вклю- чать: Операции сравнения <, <=, >, >=, ==, !=. Регулярные выражения, используемые вместе с операциями сопоставления ~ и !~. Логические операции ||, && и !. Скобки для группировки. Оператор break (если он встречается внутри циклов while или for) приводит к немедленному выходу из цикла. Оператор continue (если он встречается внутри циклов while или for) приводит к началу следующей итерации цикла. Встретившийся в awk-программе оператор next заставляет awk не- медленно перейти к следующей записи и возобновить просмотр шаб- лонов с начала программы. (Отметим различие между getline и next: getline не ведет к переходу к началу awk-программы.) Если оператор exit встречается в секции BEGIN awk-программы, программа прекращает свое выполнение и выполняется секция END (если она есть). Если оператор exit встречается в основной секции awk-программы, прекращается выполнение основной секции. Последующие записи не читаются, выполняется секция END. Оператор exit в секции END приводит к завершению выполнения программы. 6.3. Генерация отчетов Использование управляющих конструкций в секции END особенно удобно, если awk применяется в качестве средства генерации от- четов. awk позволяет составлять сводки, форматировать информа- цию, объединять ее в таблицы. В предыдущем разделе приводился пример формирования таблицы населения. В этом разделе предлага- ется еще один пример. Предположим, имеется файл prog.usage, со- держащий строки, каждая из которых состоит из трех полей: имя, программа и число использований: |Smith draw 3 |Brown eqn 1 |Jones nroff 4 |Smith nroff 1 |Jones spell 5 |Brown spell 9 |Smith draw 6 Например, первая строка означает, что Смит использует программу draw три раза. Если надо определить общее число использований каждой программы и упорядочить выходную информацию по алфавиту, можно воспользоваться следующим awk-текстом (допустим, он поме- щен в файл с именем list1): | { use [$1 " " $2] += $3 } |END { for (np in use) | print np "\t" use [np] | "sort +0 +2nr" | } Если использовать в качестве входного файл prog.usage, данная программа сформирует следующий результат: |Brown eqn 1 |Brown spell 9 |Jones nroff 4 |Jones spell 5 |Smith draw 9 Если желательно отформатировать этот результат таким образом, чтобы каждое имя печаталось только один раз, можно организовать конвейер из предыдущей awk-программы и следующей программы (ко- торая помещена в файл с именем format1): |{ if ($1 != prev) { | print $1 ":" | prev = $1 | } | print "\t" $2 "\t" $3 |} Переменная prev используется для того, чтобы убедиться, что каждое уникальное значение $1 печатается ровно один раз. Коман- да |awk -f list1 prog.usage | awk -f format1 выдаст такой результат: |Brown: | eqn 1 | spell 9 |Jones: | nroff 4 | spell 5 |Smith: | draw 9 | nroff 1 Часто оказывается удобным объединить несколько разных awk-прог- рамм с другими командами shell'а, такими как sort(1), что и бы- ло сделано в программе list1. 6.4. Взаимодействие с shell'ом Обычно awk-программа либо помещается в файл, либо указывается в командной строке (при этом ее текст заключается в одинарные ка вычки): |awk '{ print $1 }' ... Использование одинарных кавычек позволяет избежать интерпрета- ции shell'ом текста awk-программы. Это необходимо, потому что многие специальные символы awk'а совпадают со специальными сим- волами shell'а (например, $ или "). Предположим, требуется написать awk-программу, печатающую n-ое поле записи, где n - параметр, задаваемый во время запуска программы. Более подробно, мы хотим написать программу с именем field, которая по запросу |field n выполняла бы команду |awk '{ print $n }' Как передать значение n в awk-программу? Это можно сделать несколькими способами. Во-первых, можно по- местить в файл field строку |awk '{ print $'$1' }' В данном случае существенно отсутствие пробелов: эта запись воспринимается как единый аргумент, несмотря на то, что указаны две пары кавычек. $1 находится вне кавычек, доступен shell'у и, следовательно, должным образом подставится в текст программы при выполнении shell-процедуры field. Еще один способ решения задачи основывается на том, что shell интерпретирует $-параметры, которые содержатся в цепочках, зак- люченных в двойные кавычки: |awk "{ print \$ $1 }" Небольшая хитрость состоит в экранировании первого символа $ с помощью \; $1, как и в предыдущем случае, заменяется при обра- щении к field на требуемое число. 6.5. Многомерные массивы Формируя специального вида индексы, можно смоделировать много- мерные массивы. Например, в действии |for (i = 1; i <= 10; i++) | for (j = 1; j <= 10; j++) | mult [i ',' j] = ... создается массив, индексы в котором имеют вид i,j, то есть 1,1, 1,2 и так далее; тем самым моделируется двумерный массив.