163 10. AWK - ЯЗЫК СКАНИРОВАНИЯ И ОБРАБОТКИ ТЕКСТА В этом разделе описывается язык программирования, который позволяет вам легко управлять задачами обработки данных и поиска информации. С помощью awk вы можете составлять таблицы результатов обследования (наблюдения), сохраненных в файле, печатать различные отчеты, суммирующие эти результаты, изменить формат файла данных, используемый одним пакетом так, чтобы он мог быть использован и другим пакетом. Язык awk легко поддается изучению. Он автоматически выполняет многие действия, для которых в других языках вам нужно составлять программы. Как правило, многие полезные программы awk состоят из одной или двух строк. 10.1. Основные сведения об awk В этом подразделе приводится информация, достаточная для написания вами программ и их запуска. 10.1.1. Структура программы Основной операцией awk является сканирование набора вводных строк (одну за другой) для поиска строк, которые соответствуют одному из набора шаблонов или условий, которые вы указали. Для каждого шаблона вы можете указать действие, это действие выполняется с каждой строкой, которая соответствует шаблону. Структура awk: шаблон {действие} шаблон {действие} Пример. $ -"address" {print $2, $3} В этом примере приведена типичная программа awk, состоящая из одного выражения шаблон-действие. Программа печатает второе и третье поля каждой вводной строки, в которой первое поле является "address". Любой шаблон или действие в выражении шаблон-действие может быть опущен. Отсутствие части "действие" означает печать соответствующих шаблону строк. Отсутствие шаблона означает, что действие выполняется над каждой строкой. Вы можете запустить awk двумя способами. Первый: вы можете задать командную строку: awk 'шаблон-действие' [список вводных файлов] чтобы выполнить шаблон-действие в перечисленных вводных файлах. Например, awk '{print $2, $3}' файл1 файл2 Обратите внимание, что выражение шаблон-действие заключено в одиночные кавычки. Это защищает символы типа $ от интерпретации командным языком shell и позволяет также программе обрабатывать более одной строки. Если файлы не указаны в командной строке, awk читает из стандартного файла ввода. Стандартный файл ввода вы можете также указать с помощью "-". Например: awk '{print $3, $4}' файл1 - awk читает сначала из файл1 и затем из стандартного файла 164 ввода. Если программа является большой по объему, то удобнее использовать следующий формат: awk -f программа [список вводных файлов] Например, следующая командная строка говорит, что нужно выбрать и выполнить myprogram, взяв ввод из файла file1: awk -f myprogram file1 10.1.2. Поля Обычно awk считывает одну строку или запись за один раз. Записью является последовательность символов, заканчивающаяся символом "новая строка". Затем awk разделяет каждую запись на поля. Поле не может быть пустой строкой и символом табуляции. В качестве ввода для программы awk в этом разделе будем использовать файл countries, который содержит информацию о 10 странах. Каждая запись содержит имя страны, занимаемую площадь в квадратных километрах, ее население в миллионах и континент, на котором она находится. Пустое пространство между колонками является табуляцией при вводе. USSR 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 Этот файл данных удобен для обработки - смесь слов и цифр, разделенных на поля символами "пробел" и "табуляция". Число полей в записи определяется полем разделителем. Поля обычно разделяются последовательностью пробелов и/или табуляцией, так что первая запись countries будет иметь 4 поля, вторая - 5 и т.д. Возможно установить разделитель поля точно на символ табуляции, так что каждая строка будет иметь 4 поля, и можно искать значение данных. При описании по умолчанию будем использовать: поля разделяются табуляцией или пробелами; первое поле в строке обозначается $1, второе - $2 и т.д. Вводная запись обозначается $0. 10.1.3. Печать Если шаблон в выражении шаблон-действие опущен, действие выполняется для всех вводных строк. Простейшим действием является печать каждой строки, вы можете выполнить это с помощью программы awk, состоящей из одного выражения print: {print} так что командная строка: awk '{print}' countries печатает каждую строку файла countries, направляя вывод в стандартный файл вывода. Действие print может также использоваться для распечатки частей записи. Например: {print $1, $3} 165 печатает первое и третье поля каждой записи. Элементы, разделенные в выражении print запятой, разделяются при печати разделителем, которым по умолчанию является один пробел. Каждая напечатанная строка завершается разделителем, которым по умолчанию является символ новой строки. Примечание. В этом разделе будут показаны текст программы awk, без командной строки. Каждую программу можно запустить, либо заключив ее в кавычки как первый аргумент команды awk, либо поместив ее в файл и вызвать awk с флагом -f. 10.2. Печать форматированного файла Для вывода форматированного файла awk обеспечивает Си-подобное выражение printf: printf format, expr1, expr2, ..., exprn которое печатает expr в соответствии со спецификацией в строке format. Например, программа awk: {print "%10s %6d\n", &1, $3} печатает первое поле ($1) как строку из 10 символов, затем пробел, третье поле ($3) как десятичное число в шестисимвольном поле, затем новая строка (\n). Если в качестве вводного взять файл countries, то программа напечатает следующую таблицу: USSR 262 Canada 24 China 866 USA 219 Brazil 116 Australia 14 India 637 Argentina 26 Sudan 19 Algeria 18 printf не проставляет автоматически в выводном файле разделителей. Вы должны создать их сами, указав "\n" в формате спецификации. 10.3. Простые шаблоны Вы можете выбрать определенные записи для печати или другой обработки с использованием простых шаблонов. awk имеет три вида шаблонов. Первое - это шаблоны, называемые выражениями отношений, которые проводят сравнения. Например, оператор "==" тестирует на равенство. Чтобы напечатать строки, для которых 4-е поле равно строке "Asia", можно использовать программу, состоящую из одного шаблона: $4 == "Asia" Если в качестве вводного файла взять countries, то получим: USSR 8650 262 Asia China 3692 866 Asia India 1269 637 Asia Для сравнения используются: >, >=, <, <=, ==, != (не равно). Сравниваться могут числа и строки. Например, из нашего файла мы хотим распечатать только страны, население которых более 100 млн. Для этого введем: $3 > 100 166 Получим печать всех строк, в которых третье поле более 100. В качестве шаблонов могут использоваться регулярные отношения, которые позволяют осуществлять поиск указанных символов для выбора записей. Простейшей формой регулярного отношения является строка символов, обрамленная наклонными чертами: /US/ Эта программа печатает каждую строку, которая содержит буквы US. Если в качестве вводного файла взять countries, то получим: USSR 8650 262 Asia USA 3615 219 North America Третье - специальные шаблоны BEGIN и END могут использоваться для получения управления пред считыванием первой входной строки и после считывания последней. BEGIN должен быть первым шаблоном, а END - последним. Эта программа использует BEGIN для печати заголовка: BEGIN {print "Countries of Asia:"} /Asia/ {print " ", $1} На выходе получим: Countries of Asia: USSR China India 10.4. Простые действия В этом подразделе описаны встроенные и определенные пользователем переменные и функции простых действий. 10.4.1. Встроенные переменные Кроме чтения вводного файла и разбиения на поля, awk считает число считанных записей и число полей внутри текущей записи. Вы можете использовать эти счетчики в программе awk. Переменная NR - это номер текущей записи, NF - число полей в записи. Так программа: {print NR, NF} печатает номер каждой строки и число полей в этой записи. А программа: {print NR, $0} печатает каждую запись с соотвествующим номером в начале. 10.4.2. Определенные пользователем переменные awk позволяет определять пользователям собственные переменные, которые можно использовать для хранения данных, выполнения арифметических действий. Для иллюстрации, подсчитаем общее количество населения и среднее значение из файла countries: {sum = sum + $3} END {print "Общее количество населения", sum, "млн" {print "Среднее количество населения", NR, "стран", sum/NR} Первым действием является накопление значений из третьего поля каждой строки; второе действие, выполняемое 167 после последнего ввода, - напечатать сумму и среднее значение: Общее количество населения 2201 млн Среднее количество населения 10 стран 220.1 10.4.2.1. Функции Встроенные функции awk управляют арифметикой и операциями над строками. Например, функция string заменяет одну строку на другую. awk также позволяет вам определить собственные функции. 10.5. Примеры некоторых полезных программ awk может использоваться для написания больших программ некоторой сложности. В нее могут входить некоторые короткие программы, которые для вас являются полезными и поучительными. Приведем некоторые из них. Напечатать последнее поле каждой вводной строки: {print $NF} Напечатать 10-ю вводную строку: NF == 10 Напечатать последнюю вводную строку: {line = $0} END {print line} Напечатать строки, которые не имеют 4-го поля: NF != 4 {print $0, "не имеют 4-го поля" } Напечатать вводные строки, которые имеют более 4-х полей: NF > 4 Напечатать последние поля вводных строк, начиная с 5-го: $NF > 4 Напечатать общее число вводных строк: END {print NR} Напечатать обшее число полей: {nf = nf+NF} END {print nf} Напечатать общее число символов вводного файла: {nc = nc + length($0)} END {print nc + NR} Напечатать общее число строк, которые содержат строку "Asia": /Asia/ {nlines++} END {print nlines} (nlines++ имеет тот же эффект, что и nlines = nlines+1). 10.6. Сообщения об ошибках Если вы сделаете ошибку в программе awk, то получите сообщение об ошибке. Например, если вы попытаетесь запустить программу: $3 < 200 { print ($1} то получите сообщение об ошибке: awk: syntax error at source line 1 contex is 168 $3 < 200 {print >>>$1}<<< awk: illegal statement at source line 1 1 extra ( Некоторые ошибки могут быть обнаружены во время работы программы. Например, если вы попытаетесь поделить на 0 (ноль), то awk остановит обработку и распечатает номер записи вводного файла (NR) и номер строки в программе. 10.7. Шаблоны В выражении шаблон-действие, шаблон служит для выбора записей, для которых выполняется соответствующее действие. 10.7.1. Шаблоны BEGIN и END BEGIN используется для получения управления перед считыванием первой вводной строки, так что любое действие для шаблона BEGIN выполняется один раз до того, как команда awk начинает считывать первую запись. END используется для получения управления после считывания последней вводной строки. Следующая awk-программа использует BEGIN для установки в качестве разделителя символа табуляции (\t) и создания заголовков в выводном файле. Поле-разделитель хранится во встроенной переменной FS. Хотя FS может быть восстановлено в любом месте, благоразумнее поместить в секции BEGIN, до того как вводной файл начнет считываться. Второй printf в программе выполняется для каждой вводной строки и формирует выводной файл в виде таблицы, где вся информация располагается по колонкам с заголовками. END печатает результат. (Обратите внимание, что длинная строка может быть продолжена на другой строке после запятой). BEGIN { FS = "\t" printf "%10s %6s %5s %s\n", "COUNTRY", "AREA", "POP", "CONTINENT" } printf "%10s %6s %5s %s\n", $1, $2, $3, $4 area = area + $2; pop = pop + $3} END {printf "\n%10s %6d %5d\n", "TOTAL", area, pop } Если в качестве вводного взять файл countries, то получим следующий результат: COUNTRY AREA POP CONTINENT USSR 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 TOTAL 30292 2201 10.8. Выражения отношения 169 В качестве шаблона может использоваться любое выражение, вызывающее сравнение между строками символов или цифр. awk имеет 6 операторов сравнения и два регулярных выражения ~ и !~. В табл. 21 перечислены все операторы и их значение. Таблица 21 Значения операторов ---------------------------------------------------- Оператор | Значение ---------------------------------------------------- < | Меньше чем <= | Меньше или равно == | Равно != | Не равно >= | Больше или равно > | Больше чем ~ | Входит !~ | Не входит ---------------------------------------------------- При сравнении, если оба операнда являются цифровыми, то проводится цифровое сравнение; в противном случае - строчное. Например, шаблон: $3 > 100 выбирает строки в которых третье поле больше 100, а программа: $1 >= "S" выбирает строки, которые начинаются с буквы S по букву Z: USSR 8650 262 Asia USA 3615 219 North America SUDAN 986 19 Africa При отсутствии любой другой информации awk трактует поля как строки, так что программа: $1 == $4 сравнивает 1-е и 4-е поля как строки символов и для файла countries получим следующий результат: Australia 2968 14 Australia 10.9. Регулярные выражения awk обеспечивает более мощные шаблоны для поиска строки символов, чем сравнение. Такие шаблоны называются регулярными выражениями. Простейшим регулярным выражением является строка символов, обрамленная знаками "/". Например: /Asia/ Эта программа печатает все записи, которые содержат подстроку Asia (запись, содержащая Asia как часть длинной строки, подобной Asian или Pan-Asiatic, также печатается). Чтобы ограничить поиск только для специального поля, вы можете использовать операторы ~(входит) и !~(не входит). Программа: $4 ~ /Asia/ { print $1 } печатает первое поле всех тех строк, в которых четвертое поле - Asia, в то время как программа: $4 !~ /Asia/ { print $1 } печатает первое поле всех тех строк, в которых четвертое поле - не Asia. В регулярном выражении могут использоваться метасимволы: \, ^, $, ., [, ], *, ?, (, ), | 170 которые подобны метасимволам, используемым в shell. Например, метасимволы "^" и "$" осуществляют поиск соответственно начала и конца строки, а метасимвол "." ищет одиночный символ. Например: /^.$/ просматривает все записи для поиска записи, состоящей из одного символа. Если группа символов заключена в квадратные скобки, то это означает поиск одного символа из этой группы. Например, /[ABC]/ - осуществляет поиск либо символа "A", либо "B", либо "C". Границы букв или цифр могут быть обозначены внутри квадратных скобок: /[a-zA-Z]/ Если первым символом после "[" является символ "^", то это означает: любой символ, не входящий в набор. Например: /[^a-zA-Z]/ означает поиск любого символа, кроме буквы. Символ "+" означает "один или больше". Например, программа: $2 !~ /^[0-9]+$/ печатает все записи, в которых второе поле не является строкой из одной или более цифр. (^ - начало строки, [0-9]+ - одна или более цифр, $ -конец строки). Круглые скобки используются для группирования символов, а символ | для указания альтернативы. Программа: /(apple|cherry) (pie|tart)/ осуществляет поиск строк, содержащих одну из 4-х подстрок: apple pie apple tart cherry pie cherry tart Чтобы отменить специальное значение метасимвола, поставьте знак "\" перед ним. Например: /b\$/ печатает все строки, содержащие символ "b" и следующий за ним знак "$". В дополнение к распознаваемым метасимволам, awk распознает следующие последовательности языка программирования Си внутри регулярных выражений и строк: \b - возврат \f - перевод формата \n - новая строка \r - возврат каретки \t - табуляция \ddd - восьмиричное значение \" - кавычки \c - с Например, чтобы напечатать все строки, содержащие табуляцию, воспользуйтесь программой: /\t/ awk интерпретирует любую строку или переменную справа от символа "~" или "!~" как регулярное выражение. Например, мы можем записать программу: $2 !~ /^[0-9]+$/ как BEGIN { digits = "^[0-9]+&" } $2 !~ digits Предположим, что нужно найти строку символов, подобную ^[0-9]+$. Если строка "^[0-9]+$" используется как регулярное 171 выражение, появляются дополнительные знаки "\", которые защищают регулярное выражение. Это связано с тем, что первый уровень знаков "\" удаляется при синтаксическом анализе строки. Первый уровень "\" перед символом возвращает его специальное значение в регулярном выражении, второй нужен, чтобы защитить его в строке. Например, нужно найти строки, содержащие "b" и следующий за ним знак "$". Регулярное выражение для этого шаблона: b\$ Чтобы создать строку для представления этого регулярного выражения, необходимо добавить еще один символ "\": "b\\$" Следующие регулярные выражения попарно эквивалентны: x ~ "b\\$" x ~ /b\$/ x ~ "b\$" x ~ /b$/ x ~ "b$" x ~ /b$/ x ~ "\\t" x ~ /\t/ Регулярные выражения и подстроки, поиск которых они осуществляют, приведены в табл. 22. Унарные операции *, +, ? имеют наивысший приоритет, затем конкатенация и затем альтернативный выбор |. Таблица 22 Регулярные выражения ----------------------------------------------------------- Выражение | Действие ----------------------------------------------------------- с | Любой, отличный от "с" мета- | символ \с | Символ "с" ^ | Начало строки $ | Конец строки . | Любой символ, кроме новой строки [s] | Любой символ из набора "s" [^s] | Любой символ, не входящий в | набор "s" r* | Ноль или больше r+ | Один или больше r? | Ноль или один (r) | r r1r2 | Соединить r1 и r2 r1|r2 | r1 или r2 ----------------------------------------------------------- 10.10. Комбинация шаблонов Составной шаблон комбирирует простые шаблоны с логическими операторами "||" (или), "&&" (и), !(отрицание). Например, нужно напечатать все страны в "Asia" с населением более 500 млн. Следующая программа выполняет выбор всех строк, у которых 4-е поле "Asia" и третье поле превышает 500: $4 == "Asia" && $3 > 500 Программа: $4 == "Asia" || $4 == "Africa" выбирает строки с названиями "Asia" или "Africa" в 4-м поле. Эти же действия можно выполнить с помощью регулярного выражения и альтернативного оператора "|": $4 ~ /^(Asia|Africa)$/ 172 Оператор отрицания "!" имеет более высокий проиритет, чем "&&" и "||". Операторы "&&" и "||" вычисляются слева направо. Вычисление останавливается как только истина или ложь будут достигнуты. 10.11. Область шаблона Область шаблона состоит из двух шаблонов, разделенных запятой: pat1, pat2 {...} В этом случае действие выполняется для каждой строки, расположенной между pat1 и pat2 (включительно). Например, шаблон: /Canada/, /Brazil/ ищет строки со словом "Canada" до строки со словом "Brazil" Canada 3852 24 North America China 3692 866 Asia USA 3615 219 North America Brazil 3286 116 South America Также, если FNR - число текущих записей в текущем вводном файле, FILENAME - имя текущего вводного файла, то программа: FNR == 1, FNR == 5 {print FILENAME, $0} печатает первые 5 записей каждого вводного файла с именем FILENAME. 10.12. Действие В выражении шаблон-действие, "действие" определяет то, что нужно выполнить с вводными записями, которые отобраны по шаблону. Достаточно часто "действие" - это печать каких-либо выражений, но также может быть комбинацией одного или более выражений. Встроенные переменные В табл. 23 приведены встроенные переменные, которые поддерживает awk. Таблица 23 Встроенные переменные ----------------------------------------------------------- Переменная | Значение | Умолчание ----------------------------------------------------------- ARGC |Число аргументов команд-| - |ной строки | ARGV |Массив аргументов | - |командной строки | FILENAME |Имя текущего вводного | - |файла | FNR |Номер записи в текущем | - |файле | FS |Поле разделителя |пробел и/или |вводного файла |табуляция FN |Число полей в текущей | - |записи | NR |Число считанных на | - |данный момент записей | OFMT |Выводной формат для цифр| %.6g OFS |Разделитель поля вывод- | пробел 173 Продолжение табл. 23 ----------------------------------------------------------- Переменная | Значение | Умолчание ----------------------------------------------------------- |ного файла | ORS |Разделитель записи вы- | символ новой |водного поля | строки RS |Разделитель записи ввод-| то же |ного файла | RSTART |Индекс первого выбран- | - |ного символа при помо- | |щи match() | RLENGTH |Длина строки, выбранной | - |при помощи match() | SUBSEP |Нижний разделитель | "\034" ---------------------------------------------------------- 10.12.1. Арифметические действия В качестве действия могут использоваться уловные арифметические выражения, чтобы вычислить числовые значения. В качестве простого примера предположим, что нужно напечатать плотность населения для каждой страны в файле countries. Так как второе поле - это площадь в тысячах квадратных километров, а третье поле - это население в миллионах, то выражение: 100 * $3 / $2 дает плотность населения на 1 квадратный километр. Программа: {print "%10s %6.1f\n", $1, 1000 *$3 / $2} печатает название страны и плотность населения: USSR 30.3 Canada 6.2 China 234.6 USA 60.6 Brazil 35.3 Australia 4.7 India 502.0 Argentina 24.3 Sudan 19.6 Algeria 19.6 Арифметические действия выполняются с плавающей точкой. Арифметическими операторами являются: +, -, *, /, %, ^ Арифметические выражения создаются при применении этих операторов над константами, переменными, именами полей, элементами массивов, функциями и другими выражениями. awk делает присвоения подобно присвоениям в языке программирования Си. Простейшей формой присвоения является: v = e где v - переменная или имя поля; e - выражение. Например, чтобы вычислить число стран континента "Asia" и общее количество населения, вы должны написать: $4 == "Asia" { pop = pop = $3; n = n + 1 } END { print "population of", n, 174 "Asian countries in million is", pop } Относительно файла countries эта программа выдает результат: population of 3 Asian countries in million is 1765 Действие, связанное с шаблоном $4 == "Asia" выполняет 2 назначения, одно - накопление населения и другое - подсчет стран. Назначения в предыдущей программе могут быть записаны более сжато с использованием операторов "+=" и "++": $4 == "Asia" {pop += $3; ++n} Оператор "+=" заимствован из языка программирования Си, следовательно: pop += $3 аналогично: pop = pop + $3 но оператор "+=" короче и работает быстрее. Операторами назначения являются: +=, -=, *=, /=, %=, ^= Операторами приращения являются "++" и "--". Как и в языке Си они могут использоваться как префиксные (++x) или постфиксные (x++) операторы. Если x равно 1, то "i = ++x" увеличивает x, затем устанавливает i равным 2, в то время как "i = x++" устанавливает i равным 1, затем увеличивает x. Аналогичная интерпретация для префиксного и постфиксного операторов "--". Операторы присвоения, увеличения и уменьшения могут использоваться в арифметических выражениях. Мы используем установки по умолчанию в следующих программах, которые находят страны с наибольшим населением: maxpop < $3 { maxpop = $3; country = $1 } END { print country, maxpop } Обратите внимание, что эта программа будет некорректна, если значение $3 будет отрицательным. В табл. 24 перечислены встроенные арифметические функции. Таблица 24 Встроенные арифметические функции -------------------------------------------------------- Функция | Возвращаемое значение -------------------------------------------------------- atan2(y,x) |Арктангенс y/x в пределах |от "-пи" до "пи" cos(x) |Косинус x exp(x) |Экспоненциальная функция x int(x) |Целая часть x с усеченными |лидирующими нулями log(x) |Натуральный логарифм x rang() |Случайное число между 0 и 1 sin(x) |Синус x sqrt(x) |Квадрат x srand(x) |x - новое начальное значение |для rand() -------------------------------------------------------- Функция rand() возвращает псевдослучайное число с плавающей точкой в диапазоне от 0 до 1, а srand(x) может быть использовано для установки нового начального значения генерирующей программы. Если srand() не имеет аргументов, то начальное значение производится из времени дня. 175 10.13. Строки и строковые функции Строка констант - это последовательность символов, заключенная в двойные кавычки, как например, "abc", "hello, everyone". Строка констант может содержать последовательности escape языка программирования Си для специальных символов. Строковые выражения создаются путем слияния констант, переменных, имен полей, элементов массива, функций и других выражений. Программа: { print NR ":" $0 } печатает перед каждой записью ее номер и двоеточие без пробела. Три строки: номер записи, двоеточие и запись сливаются и результирующая строка печатается. В табл. 25 приведены встроенные строковые функции, поддерживаемые awk. В этой таблице r представляет собой регулярное выражение (либо как строка, либо как /r/), s и t - строковые выражения, n и p - целые числа. Таблица 25 Встроенные строковые функции awk ---------------------------------------------------------- Функция | Описание ---------------------------------------------------------- gsub(r, s) |Глобальная замена s на r в теку- |щей записи; возвращает количество |земененых символов gsub(r,s, t) |Глобальная замена s на r в строке |t, возвращает количество заменен- |ных символов index(s,t) |Возвращает позицию t в s: |0 - если t нет в s length(s) |Возвращает длину s matgch(s,r) |Возвращает позицию s, в которой |встречается r; 0 - если r не |встретилось split(s,a) |Разделяет s на массив a по FS; |возвращает число полей split(s,a,r) |Разделяет s на массив a по r; |возвращает число полей sprintf(fmt,expr-list)|Возвращает expr-list, отформати- |рованный в соответствии с форма- |том строки fmt sub(r,s) |Замещает s на первое r в текущей |записи, возвращает количество |замен sub(r,s,t) |Заменяет s на первое r в строке |t, возвращает количество замен substr(s,p) |Возвращает индекс s, начиная с |позиции p substr(s,p,n) |Возвращает подсказку s длиной n, |начиная с позиции p ---------------------------------------------------------- Функции sub и gsub сформированы после команды замены в текстовом радакторе ed. Функция gsub(r,s,t) заменяет успешное появление подстрок, найденных при помощи регулярного выражения r с заменой строки s в целевой строке t. Функция gsub(r,s) является синонимом gsub(r,s,$0). Например, программа: { gsub(/USA/, "United States"); print } 176 преобразует ввод, меняя появление "USA" на "Unites States". Функция sub подобна ей, за исключением того, что она заменяет первую найденную подстроку в целевой строке. Функция index(s,t) возвращает левую крайнюю позицию, с которой строка t начинается в s. Первый символ в строке начинается с позиции 1. Например, index("banana", "an") возвращает 2. Функция length возвращает число символов в строке; так: { print length($0), $0 } печатает каждую запись, а перед ней ее длину. ($0 не включает в вводную запись разделитель). Программа: length($1) > max { max = length($1); name = $1 } END { print name } применительно к файлу countries распечатывает наибольшее имя страны: Australia Функция match(s,r) возвращает позицию в строке s, в которой появилось регулярное выражение r, либо 0, если оно не найдено. Эта функция также устанавливает две встроенные переменные RSTART и RLENGTH. RSTART принимает значение начальной позиции, найденной в строке, это значение равно возвращаемому значению. RLENGTH принимает значение длины найденной строки. (Если строка не найдена, то RSTART равно 0, а RLENGTH равно -1). Например, следующая программа ищет появление буквы i и за ней сразу или через один символ следует буква a: { if (match($0, /i.?a/)) { print RSTART, RLENGTH, $0 } Относительно файла countries получим следующий вывод: 17 2 USSR 8650 262 Asia 26 3 Canada 3852 24 North America 3 3 China 3692 866 Asia 24 3 USA 3615 219 North America 27 3 Brazil 3286 116 South America 8 2 Australia 2968 14 Australia 4 2 India 1269 637 Asia 7 3 Argentina 1072 26 South America 17 3 Sudan 968 19 Africa 6 2 Algeria 920 18 Africa Функция sprintf(format, expr1, expr2, ..., exprn) возвращает (без печати) строку, содержащую expr1, expr2, ..., exprn, отформатированную в соответствии со спецификацией printf в строке format. Выражение: x = sprintf("%10s %6d", $1, $2) присваивает x строку, полученную при форматировании $1 и $2 как 10-символьных строк и десятичное число в поле шириной как минимум 6 знаков. Функция substr(s,p,n) возвращает подстроку s, которая начинается с позиции p и имеет длину не менее n символов. Если используется функция substr(s,p), то подстрока направляется в конец s, так что она состоит из индекса s, начинающегося с позиции p. Например, мы можем сократить имена стран в файле countries до трех символов, вызвав программу: { $1 = substr($1, 1, 3); print } В итоге получим: USS 8650 262 Asia Can 3852 24 North America 177 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 Обратите внимание, что установка $1 в программе приводит к тому, что awk заново вычисляет $0 и, кроме того, поля разделяются пробелами (значение по умолчанию для OFS), но не табуляцией. Чтобы слить строки, надо просто записать их одна за другой. Например, для файла countries: { s = s substr($1, 1, 3) " " } END { print s } печатает: USS Can Chi USA Bra Aus Ind Arg Sud Alg 10.14. Поле переменных Поля текущей записи могут ссылаться на поле переменных $1, $2, ... $NF. Эти переменные могут использоваться в арифметических или строковых операциях, им могут присваиваться различные значения. Например, вы можете разделить второе поле файла countries на 1000, чтобы площадь измерялясь не в тысячах, а в миллионах квадратных метров: { $2 /= 1000; print } или назначить новую строку полю: BEGIN { FS = OFS = "\t" } $4 == "North America" { $4 = "NA" } $4 == "South America" { $4 = "SA" } { print } Действие BEGIN устанавливает поле разделителя вводного файла (FS) и поле разделителя выводного файла (OFS) в значение табуляции. Обратите внимание, что print в четвертой строке программы печатает значение $0 после того как оно было модифицировано предыдущими присвоениями. К полям можно получить доступ при помощи выражений. Например, $(NF-1) означает: со второго до последнего поля текущей записи. Здесь необходимы круглые скобки, т.к. значение $NF-1 означает: на единицу меньше, чем значение последнего поля. Поле переменных, ссылающееся на несуществующее поле, например, $(NF+1), имеет в качестве своего начального значения пустую строку. Тем не менее новое поле может быть создано при присвоении ему значения. Например, следующая программа, вызвавшая файл countries, создает пять полей, дающих плотность населения: BEGIN { FS = OFS = "\t" } { $5 = 1000*$3/$2; print } Количество полей может изменяться от записи к записи, но обычно число их ограничивается 100 полями. 10.15. Номер или строка Переменные, поля и выражения могут иметь цифровое или строчное значение в соответствии с контекстом. Например, в 178 контексте выражения, подобного следующему: pop += $3 pop и $3 должны трактоваться как цифровые. В контексте строки, подобной: print $1 ":" $2 строки $1 и $2 сливаются. В операторах присвоения "v = e" или "op = e" тип "v" станет таким же, как и у "e". В двухсмысленном контексте: $1 == $2 тип сравнения зависит от того, являются поля цифровыми или строковыми и это будет определено только тогда, когда будет работать программа. Тип будет отличаться для каждой записи. Если два операнда являются цифровыми, то сравнение будет цифровым, если операнды являются строковыми, то сравнение - строковое. Все поля переменных имеют строковый тип; дополнительно каждое поле, которое содержит только цифры, имеет цифровой тип. Например, сравнение "$1 == $" успешно выполнится для любой части ввода: 1 1.0 +1 0.1e+1 10E-1 001 но неуспешно для: (ноль) 0 (ноль) 0.0 0a 0 1e50 1.0e50 Существуют две идиомы приведения выражения к одному или другому типу: number"" - присоединяет нулевую строку к number для приведения к строковому типу; string+0 - добавляет ноль к string для приведения к цифровому типу. Чтобы выполнить строковое сравнение между двумя полями, используйте: $1 "" == $2 "" Так значение: "12.34x" равно 12.34, а значение "x12.34" равно нулю. Значение строки арифметического выражения вычисляется путем формирования строки с преобразованием формата выводного файла. Неустановленные переменные имеют цифровое значение 0 (ноль) и строковое значение "". Несуществующие поля имеют только строковое значение ""; они не являются цифровыми. 10.16. Операторы управления потоком awk поддерживает операторы if-else, while, do-while аналогично языку программирования Си. Синтаксис оператора if: if (выражение) оператор_1 else оператор_2 "выражение" является условным и не имеет ограничений. Оно может включать операторы отношений: <, <=, >, >=, ==, != регулярные выражения: ~, !~ логические операторы: ||, &&, ! операторы слияния и круглые скобки для группирования. В операторе if awk сначала вычисляет "выражение". Если оно не ноль и не пустое, то оператор_1 выполняется, в 179 противном случае выполняется оператор_2. Часть else не является обязательной. Одиночный оператор всегда может быть заменен на набор операторов, заключенных в скобки. Каждый оператор в наборе отделяется от другого символом новой строки или точкой с запятой. Возьмем файл countries и вычислим максимальное население с помощью оператора if: { if (maxpop < $3) { maxpop = $3 country = $1 } } END { print country, maxpop } Синтаксис оператора while: while (выражение) оператор Оценивается "выражение": если оно не ноль и не пусто, то выполняется "оператор" и "выражение" вновь тестируется. Цикл повторяется до тех пор, пока "выражение" не примет значение ноль. Например, чтобы напечатать все поля вводного файла через строчку: { i = 1 while ( i <= NF ) { print $i i++ } } Синтаксис оператора for: for(выражение_1; выражение; выражение_2) оператор Он аналогичен следующей последовательности: выражение_1 while ( выражение) { оператор выражение_2 } Синтаксис оператора do: do оператор while (выражение) Оператор выполняется до тех пор, пока "выражение" не станет равным нулю. Тестирование проводится после выполнения "оператора", т.е. в конце цикла. Как правило оператор do используется реже, чем while или for. Оператор break приводит к немедленному выходу из while или for; чтобы продолжить оператор, надо начать новую итерацию. Следующий оператор заставит awk перейти к новой записи и начать поиск шаблона, начиная с первого оператора "шаблон-действие". Оператор exit завершает программу; ввод больше не считывается и действие END выполняется, если оно есть в программе. exit expr приводит к тому, что программа возвращает значение "expr" как состояние выхода. Если "expr" в строке нет, то состояние exit равно нулю. 10.17. Массивы awk поддерживает одномерные массивы. Массивы и элементы массивов нет необходимости объявлять. Индексы массива могут 180 быть числом или строкой. Пример условного обозначения числового индекса: x[NR] = $0 присваивает текущую строку вводного файла элементу NR массива x. Фактически возможно считать целый вводной файл в массив с помощью программы awk: { x[NR] = $0 } END { ... обработка ...} Первое действие только записывает каждую строку вводного файла, отмеченную номером строки, в массив x, обработка выполняется в операторе END. Элементы массива могут именоваться с помощью нецифровых величин. Например, следующая программа накапливает общее количество населения Asia и Africa в соответветствующий массив pop. Оператор END печатает общее количество населения этих двух континентов. /Asia/ { pop["Asia"] += $3 } /Africa/ { pop["Africa"] += $3 } END { print "Asian population in million is", pop[Asia] print "African population in million is", pop[Africa] } Результат получим следующий: Asian population in million is 1765 African population in million is 37 В этой программе, если вы воспользуетесь pop[Asia] вместо pop["Asia"], то выражение будет использовать значение переменной как индекса, и так как значение переменной не установлено, то количество населения будет накапливаться в pop[""]. Предположим, нужно определить общую площадь каждого континента из файла countries. Каждое выражение может быть использовано как индекс при ссылке в массиве. Так: area[ $4 ] += $2 использует строку в 4-м поле текущей записи вводного файла для индексирования массива area, накапливая значение второго поля: BEGIN { FS = "\t" } { area[$4] += $2 } END { for (name in area) print name, area[name] } Относительно файла countries получим результат: Asia 13611 North America 7467 South America 4358 Australia 2968 Africa 1888 Эта программа использует следующую форму оператора, который организует итерации для нахождения индекса в массиве: for ( i in array ) оператор выполняется "оператор" с переменной i , для которой определен array[i]. Цикл выполняется для каждого определенного индекса, который выбирается в произвольном порядке. awk не поддерживает многомерные массивы, но допускает список индексов. Они объединяются в один индекс значениями, разделенными строкой (хранимой в переменной SUBSEP). 181 Например: for ( i = 1; i <= 10; i++ ) for ( j = 1; j <= 10; j++ ) arr[i, j] = ... создает массив, который ведет себя как двумерный массив. Индексом является сочетание i, SUBSEP и j. Вы можете определить, появляется ли конкретное i в массиве arr: if ( "Africa" in arrea ) ... Это условие приведет к выполнению тестирования без создания массива ["Africa"]. Этот массив создался, бы если использовалось if ( area ["Africa"] != "" ) ... Возможно также разбить любую строку на поля, которые станут элементами массива. Это можно сделать с помощью встроенной функции split: split ( "s1:s2:s3", a, ":" ) split разбивает строку на 3 поля, используя в качестве разделителя ":" и сохраняя s1 в [1], s2 - в [2], s3 - в [3]. Возвращаемое значение этого оператора равно числу полей, т.е. трем. Третий аргумент функции split - это регулярное выражение, будет использоваться как поле разделителя. Если третий аргумент отсутствует, то в качестве поля разделителя будет использоваться FS. Массив элементов может быть разделен с помощью аргумента delete: delete имя_массива [индекс] 10.18. Функции, определенные пользователем awk поддерживает функции, определенные пользователем: function имя( список_аргументов) { операторы } Это определение может появляться в любом месте, где возможен оператор "шаблон-действие". Список аргументов - это список имен переменных, разделенных запятыми. Внутри тела функции эти переменные ссылаются на действительные переменные при вызове функции. Между именем функции и левой круглой скобкой не должно быть пробела, иначе это будет означать конкатенацию. Массив элементов просматривается при обращении, как и в Си. Функция при просмотре не может изменить значение скалярных аргументов. Внутри функции формальные параметры являются локальными переменными, но все другие переменные являются глобальными. У вас может быть любое количество формальных параметров, которые используются только как локальные переменные. Оператор return является необязательным, но если он отсутствует, возвращаемое значение будет неопределенным. 10.19. Комментарии В программе awk могут присутствовать комментарии. Они начинаются с символа # и заканчиваются символом новой строки: print x,y # это комментарий Операторы обычно занимают одну строку. Несколько операторов могут располагаться на одной строке, тогда они 182 должны разделяться точкой с запятой. Длинный оператор может располагаться на нескольких строках, причем каждая продолжаемая строка должна заканчиваться символом "\". Нельзя продолжить строку вида "....". Такое продолжение встречается редко, однако как только строка заканчивается запятой, операторы продолжаются автоматически. Примером этого служат операторы print и printf, и такое возможно после операторов "&&" и "||". Несколько операторов "шаблон-действие" могут появляться на одной строке, если они разделены точкой с запятой. 10.20. Вывод Операторы print и printf являются простейшими конструкциями, которые генерируют вывод. Оператор print используется для образования просто вывода; printf - для форматируемого вывода. Подобно shell awk позволяет вам перенаправлять вывод в файл или в канал. 10.20.1. Оператор print Оператор: print expr1, rxpr2, ..., exprn печатает строки каждого выражения, разделенные при помощи разделителей полей и следующими за ними разделителями записей. Оператор: print является сокращенной формой оператора: print $0 Чтобы напечатать пустую строку, введите: print "" 10.20.2. Разделители вывода Разделители полей выводного файла и разделители записей хранятся в строковых переменных OFS и ORS. Первоначально значение OFS устанавливается как один пробел и ORS - один символ новой строки, но эти значения могут быть изменены в любой момент времени. Например, следующая программа печатает первое и второе поле каждой записи, имеющее двоеточие между полями и два символа новой строки после второго поля. BEGIN { OFS = ":"; ORS = "\n\n" } { print $1, $2 } Обратите внимание, что : { print $1 $2 } печатает первое и второе поле без разделителя полей вводного файла. 10.20.3. Оператор printf Оператор printf, используемый в awk, подобен этому оператору в Си, за исключением того, что спецификатор * не поддерживается. Общий формат оператора: print format expr1, rxpr2, ..., exprn где format - это строка, содержащая информацию, которая 183 будет печататься, и какое преобразование будет выполняться над выражением. Каждая спецификация начинается с символа "%", заканчивается буквой, которая определяет преобразование, и может включать: - - выравнивание по левому краю в поле; width - заполнить поле на заданную ширину; поля, начинающиеся с лидирующего нуля, должны быть заполнены нулями; .prec - указывает максимальную ширину строки или разряд справа от десятичной точки. В табл. 26 приведен список символов преобразования printf. Таблица 26 Символы преобразования printf ---------------------------------------------------------- Символ | Вид печати выражения ---------------------------------------------------------- c | Один символ d | Десятичное число e | [-]d.ddddddE[+-]dd f | [-]ddd.dddddd g | e или f преобразование с подавленем | незначащих нулей o | Беззнаковое восьмиричное число s | Строка x | Беззнаковое шестнадцатиричное число % | Печать %; нет аргументов для преобразования ---------------------------------------------------------- Примеры оператора printf с соответтвующим выводом на той же строке. printf "%d", 99/2 49 printf "%s", 99/2 4.950000e+01 printf "%f", 99/2 49.500000 printf "%6.2f", 99/2 49.50 printf "%g", 99/2 49.5 printf "%o", 99 143 printf "%06o", 99 000143 printf "%x", 99 63 printf "|%10s|", "January" | January| printf "|%-10s|", "January" |January | printf "|%.3s|", "January" |Jan| printf "|%10.3s|", "January" | Jan| printf "|%-10.3s|", "January" |Jan | printf "%%" % По умолчанию формат чисел выводного файла %.6g. Он может быть изменен, если вы зададите новое значение OFMT. OFMT также управляет преобразованием цифровых значений в строковые при конкатенации и создании массива индексов. 10.20.4. Вывод в файлы В качестве стандартного вывода для печати можно использовать файлы. Для этого используются операторы изменения направления > и >>. Например, следующая программа вызывает печать из файла countries всех строк, в которых 3-е поле (население) больше, чем 100, в файл bigpop и все остальные строки в файл smallpop: $3 > 100 { printf $1, $3 > "bigpop" } 184 $3 > 100 { printf $1, $3 > "smallpop" } Обратите внимание, что имена файлов заключены в двойные кавычки; без кавычек bigpop и smallpop будут просто неустановленными переменными. Если имена выводных файлов создаются с помощью выражения, они должны быть заключены в круглые скобки: $4 ~ /North America/ { print $1 > ("tmp" FILENAME) } так как оператор > имеет более высокий приоритет, чем конкатенация. Без круглых скобок конкатенация tmp и FILENAME не будет производиться. Примечание. Файлы в программе awk открываются один раз. Если файл открыт с помощью >>, его содержимое сохраняется и вывод дополняется в файл. 10.20.5. Вывод в канал Вы можете направить печать в канал. Оператор: print | "командная_строка" направляет вывод в "командную_строку". Хотя канал здесь показан как строковая переменная, заключенная в двойные кавычки, командная строка и имена файлов могут приходить из переменных и возвращать значения из функций. Предположим, вы хотите создать список страна-население так, чтобы страны были отсортированы по алфавиту. Программа awk накапливает значение "количество населения" из 3-го поля для каждого названия страны из 4-го поля и вывод направляет в массив pop. Затем print название каждой страны и ее население направляет вывод команде sort: BEGIN { FS "\t" } { pop [$4] += $3 } END { for ( c in pop ) print c ":" pop[c] | "sort" } В результате работы этой программы получим: Africa:37 Asia:1765 Australia:14 North America:243 South America:142 Во всех этих операторах print, вызывающих перенаправление вывода, файлы или каналы идентифицируются с помощью имен (так, канал в данном примере называется "sort"), но они создаются и открываются один раз при запуске. Так что в последнем примере для всех "c" в "pop" открывается только один канал "sort". Существует ограничение на число файлов, которые могут быть открыты одновременно. Оператор close (файл) закрывает файл или канал. Когда открывается или закрывается файл, различные строки являются различными командами. 10.21. Ввод Наиболее общим способом подачи ввода программе awk является указание в командной строке имен вводных файлов. Но существуют и другие способы. Они описываются в этом подразделе. 185 10.21.1. Файлы и каналы Вы можете поместить вводимые данные в файл, указать awkdata и затем выполнить: awk 'программа' awkdata Если не указано имя файла, то awk читает из стандартного файла ввода. Например, egrep выбирает вводные строки, содержащие указанное регулярное выражение, которое может сделать это быстрее, чем awk, т.к. выполняет только это действие. И мы можем вызвать кроме того канал: egrep 'Asia' countries | awk '...' egrep быстро находит строки, содержащие "Asia" и затем направляет их программе awk для последующей обработки. 10.21.2. Разделители ввода Если используется значение по умолчанию для разделителей полей FS, то поля вводного файла разделяются символами пробела или табуляции и лидирующие символы пробелов отбрасываются, так что каждая из этих строк имеет следующее первое поле: поле 1 поле 2 поле 1 поле 1 Если в качестве разделителя полей используется символ табуляции, то лидирующие пробелы не отбрасываются. Разделитель поля может быть установлен при помощи регулярного выражения при присваивоении значения встроенной переменной FS. Например: BEGIN { FS = ",[\t] * | ([\t]+" } переделывает разделитель поля каждой строки в запятую и следующий за ней символ новой строки или табуляции, и каждую строку с символами пустой строки или табуляции без запятой. FS может быть установлен в командной строке с аргументом -F: Пример. awk -F ' (, [\t]*) | ([\t]+)' '...' а выполняет те же действия, что и в предыдущем примере. Регулярные выражения используются как разделители поля для поиска самых длинных строк (как в sub()), но не нулевых строк. 10.22. Многострочные записи Обычно записи разделяются символами новой строки, так что каждая строка яавяется записью. Такой порядок можно изменить. Если пременная RS - разделитель встроенных записей - установлен в значение "пустая строка", как в: BEGIN { RS = "" } то записи вводного файла могут занимать несколько строк. Последовательность пустых строк разделяет записи. Для обработки многострочных записей в общем случае может использоваться: BEGIN { RS = ""; FS = "\n" } установка в качестве разделителя записи символ пустой строки и разделителя поля - символ новой строки. Таким образом, каждая строка является одним полем. Однако длина записи ограничена (обычно 2500 символов). 186 10.23. Функция getline Способность awk автоматически разбивать вводной файл на записи длиной более чем одна строка, не отвечает требованиям некоторых задач. Например, если записи разделены не пустыми строками, а чем-нибудь другим, то установка RS в ноль не работает. В таком случае программа должна управлять разбиением каждой записи на поля. Здесь дано несколько советов. Функция getline может быть использована для чтения ввода либо из текущего вводного файла, либо из файла или канала, перенаправленного аналогично printf. getline вызывает следующую вводную запись и выполняет над ней нормальную операцию по разбиению на поля. Он устанавливает NF, NR, FNR. getline возвращает 1, если запись существует, 0 - если найден конец файла и -1, если появляется ошибка. (Например, невозможно открыть файл). Проиллюстрируем выше изложенное. Предположим, вы имеете вводные данные, состоящие из многострочных записей. Каждая запись начинается строкой, в начале которой стоит STOP. Следующая программа awk обрабатывает эти многострочные записи, помещая строки записи в последовательные входы массива: f[1] f[2] ... f[nf] Как только встретится строка, содержащая STOP, то запись может быть обработана в виде данных массива f: /^START/ { f[nf-1] = $0 while (getline && $0 !~ /~STOP/ } f[++nf] = $0 # now process the data in f[1] ... f[nf] ... } Обратите внимание на тот факт, что && вычисляет операнды слева направо и останавливает вычисление как только один из них будет истинным. То же самое задание может быть выполнено с помощью следующей программы: /^START/ && nf == 0 { f[nf-1] = $0 } nf > 1 { f[++nf] = $0 } /^STOP/ # now process the data in f[1] ... f[nf] ... nf = 0 } Оператор: getline x читает из файла вместо текущего ввода. Значение NR и FNR не устанавливается, но разбиение полей выполняется и устанавливается значение NF. Оператор: getline x < "file" получает следующую запись из файла и направляет в x; разбиение не производится и значение NF, NR и FNR не устанавливается. Если имя файла является выражением, то оно должно быть заключено в круглые скобки для вычисления: while ( getline x < (ARGV[1] ARGV[2]) ) { ... } т. к. операция "<" имеет больший приоритет, чем конкатенация. Без круглых скобок оператор подобный: 187 getline x < "tmp" FILENAME указывает , что нужно читать в файл "x" из файла "tmp", а не в "tmp" <значение FILENAME>. Если вы используете оператор, подобный: while ( getline x < file) { ... } то цикл будет бесконечным, если файл не может быть считан, т.к. getline возвращает -1 в этом случае. Лучше такой тест сделать с помощью следующего оператора: while ( getline x < file > 0) { ... } Вы также можете направить вывод другой команды прямо getline. Например, оператор: while ( "who" | getline ) n++ выполняет who и направляет свой вывод в getline. Каждая итерация цикла while читает более одной записи и увеличивает переменную n. После завершения цикла while, n содержит количество пользователей. Оператор: "date" | getline d направляет вывод из date в переменную d, таким образом устанавливается в d текущая дата. В табл. 27 суммируется рассказанное в этом пункте. Таблица 27 Функция getline ----------------------------------------------------- Форма | Устанавливаемое значение ----------------------------------------------------- getline | $0, NF, NR, FNR getline var | var, NR, FNR getline < file | $0, NF getline var < file | var cmd | getline | $0, NF cmd | getline | var ----------------------------------------------------- 10.24. Аргументы командной строки В программе awk могут использоваться аргументы командной строки: массив ARGV содержит элементы ARGV[0], ... ARGV[ARGC-1], где ARGC - счетчик, ARGV[0] - имя программы (в общем случае - awk), остальное - любые поддерживаемые аргументы, исключая программы и необязательные аргументы). Следующая командная строка содержит программу awk, отражающую аргументы, которые появляются после имени программы: awk ' BEGIN { for ( i = 1; i < ARGC; i++ ) printf "%s ", ARGV[i] printf "\n" }' $* Аргументы могут быть модифицированы или добавлены. ARGC может изменяться. После окончания вводного файла awk обращается к следующему ненулевому элементу ARGV (увеличив текущее значение ARGC-1) как к имени следующего вводного файла. Исключением из правила является то, что аргумент считается именем файла, если он имеет форму: var-value 188 Переменная var принимает значение value, как при операции присвоения. Если value является строкой, то кавычки не нужны. 10.25. Использование awk с другими командами и с shell Наибольшего эффекта awk достигает при использовании с другими программами. В этом подразделе обсуждаются некоторые способы взаимодействия программ awk с другими командами. 10.25.1. Функция system Встроенная функция system (command_line) выполняет команду "command_line", которая может быть строкой, вычисляющей, например, sprintf. Функция system возвращает состояние выполненной команды. Например: $1 == "#include" { gsub (/[<>"]/, $2; system ("cat " $2} вызывает команду cat для печати файла, названного во втором поле каждой вводной записи, у которой первое поле #include, после разборки каждого <, > или ", которые должны присутствовать. 10.25.2. Взаимодействие с shell Во всех приводимых примерах программа awk находилась в файле и из него осуществлялся вызов с помощью ключа -f, либо она представлялась в командной строке, заключенная в одиночные кавычки. Например: awk '{ print $1 }' ... Так как awk использует те же символы, что и shell (такие как $ и ", окружающие программу awk), одиночные кавычки обеспечат прохождение программы неизменной через shell к интерпретатору awk. Пример. Команда addr осуществляет выборку файла addresslist для получения имени, адреса и телефона. Предположим, что addresslist содержит имена и адреса, в котором типичным входом является многострочная запись, такая как: G. R. Emlin 600 Mountain Avenue Murray Hill, NJ 07974 201-555-1234 Записи разделяются одной пустой строкой. Вы можете выбрать список адресов с помощью командной строки, подобной: addr Emlin Это легко выполнить с помощью следующей программы: awk ' BEGIN { RS = "" } /Emlin/ ' addresslist Проблема состоит в том, чтобы получить различные шаблоны выборок при каждом запуске программ. Существует несколько способов сделать это. Один из способов - это создать файл, названный addr, который содержит: 189 awk ' BEGIN { RS = "" } /'$1'/ ' addresslist В программе awk один аргумент, хотя установлено два набора кавычек, но они не являются вложенными. $1 заключено в одиночные кавычки и видимо для shell; затем будет заменено на шаблон Emlin при вызове команды addr Emlin. Второй способ реализации addr полагается на тот факт, что shell заменяет параметры $ в двойных кавычках: awk " BEGIN { RS = \"\" } /$1/ " addresslist Кроме того, вы должны защитить кавычки, определяющие RS символами \, так что shell направит их awk без интерпретации. $1 распознается как параметр и shell заменяет его на шаблон, когда команда addr вызывается с шаблоном. Третий способ реализации addr - использовать ARGV для передачи регулярного выражения программе awk, которая читает список адресов с помощью getline: awk ' BEGI { RS = "" while ( getline < "addresslist" ) if ($0 ~ ARGV[1] print $0 } ' $* Вся обработка выполняется в "действии" оператора BEGIN. Обратите внимание, что регулярное выражение может быть передано addr. В частности, возможно отыскать отдельно адрес, или номер телефона, или имя. 10.26. Примеры использования awk может использоваться непредсказуемым способом: системы баз данных, различные компиляторы и трасляторы, в дополнение к традиционным задачам поиска информации, обработки данных и генерации отчетов. Программы awk значительно короче, чем аналогичные программы, написанные на традиционных языках программирования, таких как Pascal и Си. В этом подразделе приведены примеры, иллюстрирующие некоторые дополнительные возможности программ awk. 10.26.1. Генерирование отчетов awk особенно успешно применяется для выдачи отчетов, которые суммируют и форматируют информацию. Предположим, вы хотите создать отчет из файла countries, в котором континенты перечисляются в алфавитном порядке и по каждому континету страны перечисляются в убывающем по населению порядку: Africa: Sudan 19 Algeria 18 Asia: China 866 India 637 USSR 262 190 Australia: Australia 14 North America: USA 219 Canada 24 South America: Brazil 116 Argentina 26 Так как здесь несколько задач обработки данных, то намного легче выполнить этот отчет в несколько стадий. Первая: создать список троек "континент_страна_население", в котором каждое поле отделяется запятой. Это можно сделать с помощью следующей программы triplies, которая использует массив pop, индексированный в форме "континент:страна" для сохранения количества населения данной страны. Оператор print в секции END этой программы создает список троек "континент-страна-население", который направляется в программу sort: BEGIN { FS = "\t" } { pop[$4 ":" $1] += $3 } END { for ( cc in pop ) print cc "":" pop[cc] | "sort -t: +0 -1 +2nr" } Аргумент для sort заслуживает специального внимания. Аргумент -t: говорит sort, чтобы использовать ":" как разделитель полей. Аргументы +0 и -1 делают первое поле первичным ключом sort. В общем случае +i -j делают поля i+1, i+2, ... j ключом сортировки. Если j опущено, поля от i+1 до конца записи используются. Аргумент +2nr делает третье поле (цифровое уменьшение) вторичным ключом sort ( n - числовое, r - обратный порядок). Относительно файла countries эта программа выдает результат: Africa:Sudan:19 Africa:Algeria:18 Asia:China:866 Asia:India:637 Asia:USSR:262 Australia:Australia:14 North America:USA:219 North America:Canada:24 South America:Brazil:116 South America:Argentina:26 Порядок вывода правильный, но неверен формат. Чтобы преобразовать формат вывода в требуемую форму, запустите программу format с этими данными: BEGIN { FS = ":" } { if ($1 != prev) { print "\n" $1 ":" prev = $1 } printf "\t%-10s %6d\n", $2, $3 } Эта программа прерывания управления печатает только первое появление имени континента и форматирует строки "страна-население", соответствующие этому континенту, требуемым способом. Командная строка: awk -f triplies countries | awk -f format дает требуемый отчет. В этом примере предполагается, что сложные задачи преобразования данных и их форматирования 191 могут быть сокращены до нескольких простых команд awk и сортировки. 10.26.2. Дополнительные примеры 10.26.2.1. Частота использования слов Первый пример иллюстрирует связанные массивы для подсчета. Предположим, вы хотите подсчитать сколько раз каждое слово появляется во вводе, где "слово" - это любая непрерывная последовательность символов, отличных от пустого символа и символа табуляции. Следующая программа печатает частоту появления слов, отсортированных в убывающем порядке: { for ( w = 1; w <= NF; w++ ) count[$w]++ } END {for( w in count) print count[w], w | " sort -nr" } Первый оператор использует массив count для накопления количества появлений каждого слова. Как только ввод будет считан, второй оператор цикла for направляет окончательный счетчик каждого слова команде sort. 10.26.2.2. Накопление Предположим вы имеете два файла deposite и withdrawals, записи которых содержат имя поля и количество полей. Для каждого имени вы хотите напечатать итог net, определяющийся вычитанием общего вывода из общего депозита. Баланс net может быть вычислен следующей программой: awk ' FILENAME == "deposits" { balance[$1] += $2 } FILENAME == "withdrawals" { balance[$1] -= $2 } END { for (name in balance ) print name, balance[name] }' deposits withdrawals Первый оператор использует массив balance для накопления общего количества для каждого имени в файле deposits. Второй оператор вычитает соответствующий вывод из каждого общего депозита. Оператор END печатает каждое имя с соответствующим итогом. 10.26.2.3. Случайный выбор Следующая функция печатает случайные элементы k, начиная с первого элемента массива A, состоящего из n элементов. В программе k - это количество входов, необходимых для печати, n - количество элементов, которые еще будут исследоваться. Выбор печатать или нет i-тый элемент определяется тестом rand() < k/n: function choose (A, k, n, i) { for (i = 1; n > 0; i++) if (rand() < k/n--) { print A[i] k-- } } } 10.26.2.4. Возможности shell Следующая программа awk приблизительно моделирует 192 возможности shell системы UNIX. Строка, содержащая только знак "=" заново выполняет последнюю выполненную команду. Строка, начинающаяся с =cmd заново выполняет последнюю команду, вызов которой включает строку cmd. Иначе выполняется текущая строка. $1 == "=" { if [NR == 1] system ( x[NR] = x [NR-1] ) else for ( i= NR-1]; i > 0; i-- ) if ( x[i] ~ $2 ) { system(x[NR] = x[i]) break } next } /./ { system(x[NR] = $0) } 10.27. Итоговое краткое описание awk Командная строка awk programm filenames awk -f programm-file filenames awk -Fs sets field separator to string s awk -Ft sets separator to tab Шаблоны BEGIN END /regular expression/ relational expression pattern && pattern pattern || pattern (pattern) !pattern pattern, pattern Операторы управления потоком if (expr) statement [else statement] if (subscript in array) statement [else statement] while (expr) statement for (expr; expr; expr) statement for (var in array) statement do statement while statement break cintinue next exit [expr] return [expr] Ввод-вывод close (filename) закрыть файл getline установить $0 из следующей вводной записи; установить NF, NR, FNR getlinefile печать выражений в файл printf fmt, expr-list отформатировать и распечатать printf fmt, expr-list >file отформатировать и распечатать в файл system (cmd-line) выполнить команду cmd-line, возвратить состояние В print и printf >>file добавляется в file и |command - записывает в канал. Функции func name(parameter list) { statement } function name(parameter list) { statement } function-name(expr, expr, ...) Функции строки gsub(r,s,t) заменить строку s для каждого найденного регулярного выражения r в строке t; возвращает количе- ство замен; если t опущено, то используется $0 index(s,t) возвращает индекс строки t в строке s, или 0, если нет вхож- дений строки t length(s) возвращает длину строки s match(s,r) возвращает позицию s, в которой встретилось регулярное выражение r; возвращает 0, если r не найдено split(s,a,r) разбить строку s в массив a по регулярному выражению r; возвра- щает количество полей; если r опущено, то используется значе- ние FS sprints(fmt,expr-list) печатает expr-list в соответствии с fmt, возвращает результирующую строку sub(r,s,t) аналогично gsub, за исключением того, что заменяется только первая найденная подстрока substr(s,i,n) возвращает подстроку n, начина- ющуюся с i; если n опущено, то используется остаток s Арифметические функции atan2(y,x) арктангенс y/x в пределах от "-пи" до "пи" cos(x) косинус x exp(x) экспоненциальная функция x 194 int(x) целая часть x с усеченными лидирующими нулями log(x) натуральный логарифм x rang() случайное число между 0 и 1 sin(x) синус x sqrt(x) квадрат x srand(x) x - новое начальное значение для rand() Операторы = += -= *= /= %= ^= присвоение ?: условное выражение || логическое OR && логическое AND ~ !~ поиск регулярного выражения; отрицательный поиск < <= > >= != == отношения blank конкатенация строк + - сложить, вычесть * / % умножить, разделить, режим + - ! унарный плюс, унарный минус, логическое отрицание ^ показательная функция ( ** является синонимом) ++ -- приращение, отрицательное приращение $ поле Регулярные выражения с поиск на совпадение с немета- символом "c" \с поиск буквенного символа "с" ^ поиск начала строки или последовательности строк $ поиск конца строки или последовательности строк . поиск любого символа, кроме символа новой строки [s] поиск любого символа из набора "s" [^s] поиск любого символа, отличного от "s" и символа новой строки r* поиск ноль или больше r+ поиск одного или больше r? поиск ноль или один (r) группирование: поиск r r1r2 конкатенация: поиск r1 затем r2 r1|r2 поиск либо r1 либо r2 Встроенные переменные ARGC число аргументов командной строки ARGV массив аргументов командной строки FILENAME имя текущего вводного файла FNR номер записи в текущем файле FS разделитель поля вводного файла; (по умолчанию - пробел) 195 FN число полей в текущей записи NR число считанных на данный момент записей OFMT выводной формат для цифр; (по умолчанию - %6.g) OFS разделитель поля выводного файла ORS разделитель записи выводного поля RS разделитель записи вводного файла RSTART индекс первого выбранного символа при помощи match(); 0 - если символ не найден RLENGTH длина строки, выбранной при помощи match() -1 - если строка не найдена SUBSEP разделитель индексов элементов массива; (по умолчанию - \034) 10.27.1. Ограничения При работе с awk вы должны придерживаться следующих ограничений: 100 полей 2500 символов во вводной записи 2500 символов в выводной записи 1024 символов в индивидуальном поле 1024 символов в строке printf 400 символов в строке, заключенной в кавычки 400 символов в классе символов 15 открытых файлов 1 канал 10.27.2. Инициализация, сравнение и тип приведения Каждые переменная или поле могут потенциально быть строкой или числом, либо состоять из того и другого. Когда значение переменной устанавливается при присвоении: var = expr то тип переменной определяется выражением. В арифметических выражениях тип - цифровой, в конкатенации - строковый, и т.д. Если назначение является простым копированием: v1 = v2 то типом v1 становится тип v2. При сравнении, если оба операнда являются цифровыми, то производится цифровое сравнение. В противном случае, операнды рассматриваются как строковые и сравнение производится над строками. Тип любого выражения может быть приведен к цифровому таким образом: expr + 0 и к строковому типу: expr "" (это конкатенация с пустой строкой) Инициализированные переменные имеют цифровое значение 0, а строковые - значение "". Соответственно, если x проинициализировано, то оператор if (x) ... имеет значение "ложь", а if (!x) ... if (x == 0) ... if (x == "") ... все являются истиной, но if (x == "0") ... является ложью. 196 Тип поля определяется по контексту. Например: $1++ означает, что $1 будет цифровым, и $1 = $1 "," $2 означает, что и $1 и $2 являются строковыми. Приведение к типу выполняется при необходимости. Если по контексту тип не может быть определен, например: if ($1 == $2) ... тип поля определяется при вводе. Поля, которые являются нулевыми, имеют строки со значением "", они не являются цифровыми. Определения типов для элементов массива, созданных split(), аналогичны определению типов для полей. Так, если arr[i] не существует, то : if (arr[i] == "") ... приводит к тому, что он появляется со значением "".