Язык обработки структурированных текстов AWK
Производственно-внедренческий кооператив
"И Н Т Е Р Ф Е Й С"
Диалоговая Единая Мобильная
Операционная Система
Демос/P 2.1
Язык обработки структурированных текстов AWK
Москва
1988
Аннотация
Язык AWK используется для комбинированной обработки
символьных и числовых полей в записях. В результате генери-
руется отчет в запланированной программистом форме. Прог-
раммы на языке AWK можно эффективно использовать как фильтры
данных для преобразования вывода одной программы и передачи
результата фильтрации на вход другой. В системе ДЕМОС уста-
новлен интерпретатор языка AWK, который получил название
awk.
1. Принципы работы интерпретатора awk
Любой текст имеет некоторую структуру, в простейшем
случае ее элементами являются строки и слова текста. В
языке AWK текст рассматривается как список записей и полей в
них и на этой основе выполняется некоторый определенный
программистом алгоритм обработки. Допустим, имеется следую-
щий текст:
Сидоров Сидор Сидорович 1957 г.р. 220 руб сл.
Петров Петр Иванович 1962 г.р. 200 руб сл.
Иванов Михаил Константинович 1965 г.р. 180 руб раб.
Волков Леонид Николаевич 1950 г.р. 280 руб раб.
Семенов Петр Михайлович 1958 г.р. 210 руб раб.
Этот текст структурирован: записи - это строки, поля в стро-
ках - слова и числа. В каждой записи содержится по 8 полей,
разделяющихся пробелами. Значащим (в качестве разделителя)
является только один пробел между полями, остальные игнори-
руются. Рассмотрим несколько простых программ на языке AWK.
Пример 1. AWK-программа выводит первые три поля из
восьми, порядок полей в выводе изменен и перед каждой стро-
кой печатается символ табуляции
{ print( "\t", $2, $3, $1 ); }
Оператор print выполняется для всех входных записей. После
выполнения программы получим:
Сидор Сидорович Сидоров
Петр Иванович Петров
Михаил Константинович Иванов
Леонид Николаевич Волков
Владимир Михайлович Семенов
Как видно из программы, значения полей подставляются следую-
щим образом:
$номер_поля_в_записи
Первому полю соответствует 1. В общем случае номером поля
может быть значение выражения. Значением подстановки $0
является вся запись.
Пример 2. AWK-программа выводит номера строк после
табуляции
{ print( "\t", NR, $2, $3, $1 ); }
После выполнения программы получим:
- 3 -
1 Сидор Сидорович Сидоров
2 Петр Иванович Петров
3 Михаил Константинович Иванов
4 Леонид Николаевич Волков
5 Владимир Михайлович Семенов
Предопределенная переменная NR равна номеру обрабатываемой
записи. Мы воспользовались ее значением для нумерации
строк.
Пример 3. AWK-программа выводит полное число лет на
1988 год каждому лицу из списка
{ print("\t", NR, $2, $3, $1, "\t\t", 1988 - $4); }
После выполнения программы получим:
1 Сидор Сидорович Сидоров 31
2 Петр Иванович Петров 26
3 Михаил Константинович Иванов 23
4 Леонид Николаевич Волков 38
5 Владимир Михайлович Семенов 30
Пример 4. AWK-программа подсчитывает средний возраст и
среднюю заработную плату перечисленных в списке лиц
{
age += 1988 - $4;
pay += $6;
}
END {
print ("Средний возраст:\t", age/NR );
print ("Средняя зарплата:\t", pay/NR );
}
После выполнения программы получим:
Средний возраст: 29.6
Средняя зарплата: 218
Когда необходимо обеспечить вывод результата по завершению
списка записей, используется селектор END. Переменные age и
pay определяются автоматически как числа в момент первого
использования. Выражения вычисляются для всех входных запи-
сей.
Пример 5. AWK-программа подсчитывает средние возраст и
заработную плату рабочих и служащих в списке. Для выделения
строк со сведениями о рабочих используется шаблон /раб/, о
служащих - шаблон /сл/. Шаблоны содержат образцы для поиска
в полях записи. Данные выводятся после обработки всех
- 4 -
записей.
/раб/ {
rage += 1988 - $4;
rpay += $6;
r++;
}
/сл/ {
age += 1988 - $4;
pay += $6;
c++;
}
END {
print("\t\tСредний возраст Средняя зарплата\n");
print(" Рабочие:\t", rage/r, "\t", rpay/r );
print("Служащие:\t", age/c, "\t\t", pay/c );
}
После выполнения программы получим:
Средний возраст Средняя зарплата
Рабочие: 30.3333 223.333
Служащие: 28.5 210
Программа выполняется следующим образом. Если запись в
каком-либо из полей содержит образец, выполняется действие,
записанное в фигурных скобках рядом с соответствующим шабло-
ном, иначе действие не выполняется. Действия, указанные
после END, выполняются по концу списка записей. Шаблоны в
примере используются как селекторы входных записей: если в
четвертом примере действия были выполнены для всех входных
записей, то в этом - только для отобранных по образцам, ука-
занным в шаблонах. При этом END используется как селектор
специального вида: он определяет список операторов AWK-
программы, который должен выполниться после завершения вход-
ного потока записей.
Пример 6. AWK-программа вычисляет уровни заработной
платы
- 5 -
BEGIN {
Min = 1000;
Max = 0;
}
{
if ( $6 << Min ) {
Min = $6;
smin = $1 " " $2 " " $3;
}
if ( $6 >> Max ) {
Max = $6;
smax = $1 " " $2 " " $3;
}
}
END {
print( "\t\tУровни зарплаты\n" );
print( " Минимальный: ", Min, " (",smin,")" );
print( "Максимальный: ", Max, " (",smax,")" );
}
После выполнения получим:
Уровни зарплаты
Минимальный: 180 ( Иванов Михаил Константинович )
Максимальный: 280 ( Волков Леонид Николаевич )
В этой программе три раздела. Первый раздел используется для
установки начальных значений переменных Max и Min еще до
чтения записей из списка. Специальный селектор BEGIN опре-
деляет список операторов AWK-программы, который должен
выполниться до анализа первой записи из входного потока. Во
втором разделе осуществляется собственно обработка записей.
Операторы этого раздела программы выполняются для всех вход-
ных записей, так как селектор не указан. Третий раздел
выполняется когда завершается список записей (селектор END).
В строке
smin = $1 " " $2 " " $3;
переменной smin присваиваются значения первых трех полей
записи, конкатенация которых вместе с пробелами, указанными
в кавычках, образует строку. Таким образом значением пере-
менной smin будет строка символов типа "Фамилия Имя
Отчество".
Существует несколько способов вызова интерпретатора
awk. AWK-программа в файле:
- 6 -
awk -f имя_файла_с_AWK-программой входной_файл ...
По умолчанию разделителем записей является символ новой
строки, разделителем полей - символ пробела и/или табуляции.
Символы-разделители можно явно определить в программе.
Символ-разделитель полей можно определить и в командной
строке. Вызов awk с указанием символа-разделителя полей:
awk -Fразделитель -f файл_AWK-программа входной_файл ...
Часто AWK-программы настолько коротки, что их целесообразно
указывать непосредственно в командной строке, а не в файле.
Вызов awk с программой в командной строке:
awk -Fразделитель 'AWK-программа' входной_файл ...
awk 'AWK-программа' входной_файл ...
Интерпретатор awk, как и большинство других программ сис-
темы, позволяет входной_файл заменить на стандартный ввод.
awk -f имя_файла_с_AWK-программой -
awk -Fразделитель 'AWK-программа' -
awk 'AWK-программа' -
Если не указано другое, результат выполнения AWK-программы
печатается на экране дисплея.
2. Переменные, выражения и присваивания в AWK-программах
В языке AWK выделяют две группы переменных: предопреде-
ленные и декларированные в программе. Предопределенные
переменные доступны для подстановок и изменений в программе,
их исходные значения устанавливаются интерпретатором awk в
процессе запуска и выполнения AWK-программы. К предопреде-
ленным переменным относятся:
NR номер текущей записи;
NF число полей в текущей записи;
RS разделитель записей на вводе (символ);
FS разделитель полей записи на вводе (символ);
ORS разделитель записей на выводе AWK-программы (символ);
OFS разделитель полей записи на выводе (символ);
- 7 -
OFMT формат вывода чисел;
FILENAME имя входного файла (строка).
По умолчанию имеют место следующие значения предопреде-
ленных переменных:
RS = "\0";
FS = 'пробел(ы) и/или табуляция';
OFS = FS;
ORS = RS;
OFMT = "%.6g";
Предопределенным переменным RS, FS, ORS, OFS, OFMT можно
присваивать значения в AWK-программе.
В языке AWK отсутствуют декларация и явная инициализа-
ция переменной любого типа. Всякой переменной до ее первого
использования присваивается значение "\0" - пустая строка.
Применяются следующие типы переменных:
позиционная переменная;
число с плавающей точкой;
строка символов;
массив.
Позиционная переменная определяет поле записи, содержимое
которого может быть отнесено к типам "строка" или
"число_с_точкой" и используется в виде
$номер_поля_записи
$(выражение)
Номер_поля_записи может быть значением выражения. Значением
позиционной переменной $0 является вся запись.
Интерпретатор awk рассматривает переменную как строко-
вую до того момента, когда необходимо выполнить некоторую
операцию над ее значением. В зависимости от контекста тип
значения переменной остается либо строковым, либо преобразу-
ется к типу число_с_точкой. В двусмысленных случаях пере-
менные рассматриваются как строковые. Строки, которые не
могут быть интерпретированы как числа, в числовом контексте
будут иметь числовое значение НОЛЬ. Устранить двусмыслен-
ность можно явным указанием типа переменной при присваивании
ей значения, например:
- 8 -
name = 1 ; # присвоено значение 1.0
name = "1"; # присвоено значение строки "1"
При интерпретации выражений существенную роль играет кон-
текст, например:
name = 3 + 2 ;
name = 3 + "2" ;
name = "3" + "2" ;
name = 3 + 2 + "яблоко груша апельсин";
name = "яблоко" + "груша";
В этом примере в первых четырех случаях name равно 5.0, в
пятом - 0.
Массив не декларируется, он начинает существовать в
момент первого использования. Индексы в массиве могут иметь
любое ненулевое значение, включая нечисловые строки, это
позволяет использовать ассоциативные массивы. Например, в
приведенной ниже AWK-программе будет подсчитано число упоми-
наний об автомобилях различных марок во входном тексте:
/ЗИЛ/ { Автомобили["ЗИЛ"]++; }
/ГАЗ/ { Автомобили["ГАЗ"]++; }
/ВАЗ/ { Автомобили["ВАЗ"]++; }
END {
print("ЗИЛ : ", Автомобили["ЗИЛ"]);
print("ГАЗ : ", Автомобили["ГАЗ"]);
print("ВАЗ : ", Автомобили["ВАЗ"]);
}
Массивы можно использовать для организации такого алгоритма
обработки данных, в котором требуется многократный просмотр
входного потока записей. Например, если не заботиться о
размерах оперативной памяти, то можно весь входной файл
записать в виде массива записей и по завершению входного
потока приступить собственно к обработке:
- 9 -
{
Массив_записей[NR] = $0
}
END {
...
программа обработки массива
...
}
В качестве имени (значения) индекса массива можно использо-
вать выражение, например:
name["2" * $3]
В языке AWK используются операторы присваивания
= += -= *= /= %=
и арифметические операции
+ - * / % ++ --
Они имеют тот же смысл, что и в языке программирования Си.
Имеются некоторые особенности выполнения операций срав-
нения
<< <<= == != >>= >>
Если оба операнда интерпретируются как числа, то выполняется
сравнение чисел. Если один из операндов является строкой
символов, а другой - числом, то выполняется сравнение строк.
Сравнение строк заключается в попарном сравнении внутренних
кодов символов строк до первого неравенства кодов или до
завершения одной из строк. Рассмотрим пример:
{
if( $1 << $2 )
print(NR": $1 =", $1, "; $2 =", $2, "; $1 < $2");
if( $1 == $2 )
print(NR": $1 =", $1, "; $2 =", $2, "; $1 == $2");
if( $1 >> $2 )
print(NR": $1 =", $1, "; $2 =", $2, "; $1 > $2");
}
Допустим, имеется следующий входной текст:
- 10 -
2.01 2.02
2.01 abc
a b
aa b
aa ab
aa ba
abc ab
ab abc
ef abc
В результате выполнения программы получим:
1: $1 = 2.01; $2 = 2.02; $1 < $2
2: $1 = 2.01; $2 = abc ; $1 < $2
3: $1 = a ; $2 = b ; $1 < $2
4: $1 = aa ; $2 = b ; $1 < $2
5: $1 = aa ; $2 = ab ; $1 < $2
6: $1 = aa ; $2 = ba ; $1 < $2
7: $1 = abc ; $2 = ab ; $1 > $2
8: $1 = ab ; $2 = abc ; $1 == $2
9: $1 = ef ; $2 = abc ; $1 > $2
В AWK-программах можно использовать следующие логичес-
кие операции:
! (не) || (или) && (и)
Как обычно, значением выражения, содержащего операции отно-
шения и/или логические операции, являются: истина (не ноль)
или ложь (ноль). Приоритеты операций в выражениях анало-
гичны установленным в языке Си. Для управления порядком
выполнения операций в выражении используются круглые скобки.
В языке AWK имеется операция, не предусмотренная в Си,
- это операция "пробел", которая используется для конкатена-
ции переменных, значения которых интерпретируются как стро-
ковые
name = "яблоко " "и груша";
В этом случае значением переменной name будет строка вида
"яблоко и груша"
Вместо символа пробел можно использовать символ табуляции.
При использовании операции "пробел" учитывается контекст,
например:
- 11 -
$1 = "яблоко"
$2 = "и"
$3 = "груша"
name1 = $3 $2 $1; # 1
name2 = $3" "$2" "$1; # 2
name3 = "Красное " $1; # 3
name4 = 1 2 3 4 5 6 7 8 9; # 4
name5 = 123 789; # 5
name6 = $3$2$1; # 6
name7 = $3 123; # 7
значением переменной name1 будет строка:
"грушаияблоко"
Значением переменной name2 будет строка:
"груша и яблоко"
Значением переменной name3 будет строка:
"Красное яблоко"
Значением переменной name4 будет строка:
"123456789"
Значением переменной name7 будет строка:
"груша123"
Значением переменной name5 будет строка:
"123789"
Из примера 5 видно, что в качестве знака операции "пробел"
существенно наличие лишь одного пробела между операндами,
остальные игнорируются. Значением переменной name6 будет
строка вида
"грушаияблоко"
Однако синтаксис, использованный в 6 строке примера, сомни-
телен и не стоит полагаться на "мудрость" интерпретатора
awk.
Позиционные переменные можно использовать в выражениях
любого вида, им можно присваивать новые значения. Рассмотрим
несколько примеров:
- 12 -
$3 = $1 " " $2;
$3 += $1;
$3 = $3 $3 $3;
$3 = "";
$0 = $3;
В первом случае позиционной переменной $3 присваивается
строка, полученная в результате конкатенации значения пози-
ционной переменной $1, пробела и значения позиционной пере-
менной $2. Во втором случае значение переменной $3 увеличи-
вается на значение переменной $1. В третьем случае выполня-
ется конкатенация собственного значения переменной $3, в
четвертом - переменной $3 присваивается значениие пустой
строки, в пятом - значение переменной $0 (вся запись) заме-
няется значением поля 3.
3. Структура AWK-программы
AWK-программа состоит из списка правил вида:
селектор1 { действие }
...
селекторN { действие }
Открывающая фигурная скобка должна стоять в той же строке,
где селектор. В любом месте программы можно ввести коммента-
рий, он печатается от символа # до конца строки.
Каждое правило выполняется для каждой записи из вход-
ного потока. Селектор используется для того, чтобы выделить
запись, над которой будет выполнено действие соответствую-
щего правила. Если запись не выделена ни одним из селекто-
ров, она игнорируется и не выводится на стандартный вывод.
Если запись выделена селектором, выполняется действие соот-
ветсвующего правила. Если некоторую запись выделяют нес-
колько селекторов, над ней выполняются действия соответству-
ющих правил.
В правиле может отсутствовать селектор, тогда действие
этого правила будет выполнено для всех без исключения вход-
ных записей. В правиле может отсутствовать действие, тогда
все выделенные селектором записи будут направлены на стан-
дартный вывод без изменений.
Определены два правила специального вида:
- 13 -
BEGIN { действие }
...
список других правил
...
END { действие }
Правило с селектором BEGIN выполняется до чтения первой
входной записи, с селектором END - после чтения последней
записи. Правило с селектором BEGIN должно быть первым в
списке правил, с селектором END - последним. Возможно такое
использование этих правил:
BEGIN { действие }
...
список других правил
или
список других правил
...
END { действие }
Действие в правиле может содержать список операторов и
управляющих конструкций. Оператор должен заканчиваться сим-
волом ";", или символом новой строки, или закрывающей скоб-
кой.
Переменную можно использовать в любом правиле AWK-
программы, начиная с места, где она определена. Рассмотрим
пример, в котором демонстрируются особенности выполнения
правил AWK-программы и использования переменных:
- 14 -
# Программа демонстрирует работу
# правил различного вида и область
# действия переменных
# Правило 1 выполняется
# для всех записей
{ print("Запись номер:", NR); }
# Правило 2 выполняется только для
# записей, где обнаружен образец aaa
/aaa/ {
print("Правило 2:");
print(" Вход:", $0);
$1 = $1 $2;
$2 = "***";
A = $2;
print("Результат:", $0, "A =", A);
}
# Правило 3 выполняется только для
# записей, где обнаружен образец ddd
/ddd/ {
print("Правило 3:");
print(" Вход:", $0);
$1 = $1 $3;
$2 = "&&&";
A = $2;
print("Результат:", $0, "A =", A);
}
# Правило 4 выполняется для всех записей
{
print("Правило 4:", $0, "A =", A,"\n");
}
Допустим, на вход этой программе передаются следующие три
записи:
eee fff
ddd eee fff
aaa bbb ccc ddd eee
тогда после выполнения программы получим:
- 15 -
Запись номер: 1
Правило 4: eee fff A =
Запись номер: 2
Правило 3:
Вход: ddd eee fff
Результат: dddfff &&& fff A = &&&
Правило 4: dddfff &&& fff A = &&&
Запись номер: 3
Правило 2:
Вход: aaa bbb ccc ddd eee
Результат: aaabbb *** ccc ddd eee A = ***
Правило 3:
Вход: aaabbb *** ccc ddd eee
Результат: aaabbbccc &&& ccc ddd eee A = &&&
Правило 4: aaabbbccc &&& ccc ddd eee A = &&&
4. Селекторы
Селектор указывается, чтобы определить, будет ли выпол-
няться действие в правиле. В качестве селектора может быть
использовано любое выражение, шаблон и произвольная их ком-
бинация. Рассмотрим несколько примеров использования выраже-
ний в селекторах:
$1 != $2 || $1 >> 128
выбрать запись, в которой либо первые два поля раз-
личны, либо содержимое первого поля больше 128;
$1 % $2 == 1
выбрать запись, в которой остаток от деления полей
равен 1;
NF % 2 == 0 || name << 2.2
выбрать запись, либо содержащую четное число полей,
либо если переменная name меньше 2.2;
$1 == "Иванов И.И."
выбрать запись, в которой первое поле относится к Ива-
нову И.И.;
$1 >>= "М" && $1 != "Москва"
выбрать запись, первое поле которой начинается с буквы
М и далее по алфавиту, но не является словом Москва.
Шаблон используется для формирования одного или боль-
шего числа образцов в селекторе. При сканировании входной
записи осуществляется поиск цепочки символов, тождественной
- 16 -
образцу. В простейшем случае селектор с шаблоном выглядит
следующим образом:
/образец/
В символах / указан образец, который будет использован для
поиска. Существенно, что любой символ, в том числе пробел,
указанный внутри пары символов /, является частью образца.
Если необходимо, чтобы соответствие образцу определя-
лось в конкретном поле записи, используются операторы соот-
ветствия (~ и !~)
$номер_поля ~ шаблон
если при просмотре указанной позиционной перменной
обнаруживается цепочка символов, тождественная образцу
в шаблоне (оператор ~), выполняется действие правила.
$номер_поля !~ шаблон
если при просмотре указанной позиционной перменной не
обнаруживается цепочка символов, тождественная образцу
в шаблоне (оператор !~), выполняется действие правила.
В общем случае шаблон может формировать множество
образцов и/или указывать, в каком месте записи необходимо
искать соответствие входной цепочки символов образцу. При
необходимости используются так называемые регулярные выраже-
ния, в этом случае шаблон выглядит следующим образом:
/регулярное_выражение/
В результате разбора регулярного выражения интерпретатором
awk строится и выполняется алгоритм поиска одного или боль-
шего числа образцов во входной записи.
Регулярные выражения в шаблонах селекторов AWK анало-
гичны подобным в lex, редакторе ed и в команде grep. Регу-
лярное выражение формируется как композиция цепочек символов
(и/или диапозонов символов) и операторов. Операторы в регу-
лярных выражениях указываются в виде символов-операторов.
Чтобы отнести действие символа-оператора к отдельному фраг-
менту регулярного выражения, используются круглые скобки.
Чтобы отменить специальное значение символа-оператора, его
экранируют символом \.
Для записи регулярных выражений употребляются следующие
символы-операторы:
^ от начала;
$ на конце;
- 17 -
. любой символ;
символ
данный символ, если он не символ-оператор;
\символ
использовать символ-оператор как обычный символ;
[строка]
любой из символов данной строки;
[буква1-буква2]
любая буква из данного лексикографически упорядоченного
диапазона букв;
[цифра1-цифра2]
любая цифра из данного диапазона цифр;
рег_выражение*
0 или более вхождений регулярного выражения;
рег_выражение+
1 или более вхождений регулярного выражения;
рег_выражение?
0 или 1 вхождение регулярного выражения;
рег_выражение1 рег_выражение2
последовательное вхождение рег_выражение1 и
рег_выражение2;
рег_выражение1|рег_выражение2
вхождение рег_выражение1 или рег_выражение2;
Рассмотрим несколько примеров использования регулярных
выражений:
/^Иванов/
выделить запись, начинающуюся цепочкой символов "Ива-
нов" Таким образом, будут выделены случаи типа "Ива-
нову", "Ивановой", ... ;
$3 ~ /^Иванов/
выделить запись, в которой третье поле начинается
цепочкой символов "Иванов";
/([abc][ABC])$/
выделить запись, предпоследним символом которой явля-
ется одна из букв abc и последним - одна из букв ABC;
/[0-9]+/
выделить запись, содержащую не менее одной цифры;
- 18 -
$3 !~ /(Сидор)|(Петр)/
не выделять запись, содержащую в третьем поле что-либо
о Сидорах или Петрах;
Ниже приведен пример AWK-программы, печатающей имена
регистрационных каталогов и имена всех пользователей сис-
темы, которыми не установлен пароль:
BEGIN {
FS = ":";
print("Имя\tКаталог");
}
$2 !~ /(([0-9])|([a-z])|([A-Z]))+/ {
print( $1, "\t", $6);
}
В первом правиле (селектор BEGIN) меняется разделитель полей
записи с пробела на двоеточие (такова структура записей в
парольном файле /etc/passwd операционной системы ДЕМОС). Во
втором поле записи парольного файла содержится зашифрованный
пароль - обычно это комбинация цифр и букв. Если пароль не
установлен, то второе поле записи пусто. Этот факт использо-
ван для формирования селектора - второе поле не должно
содержать цифр и букв. Селектор выделяет второе поле записи
и проверяет наличие не менее одного символа в этом поле.
Если поле пусто, выполняется действие, которое заключается в
печати имени пользоватея (первое поле) и имени регистрацион-
ного каталога пользователя (шестое поле).
Иногда необходимо определить диапазон записей, для
которых выполняется действие. Например, необходимо вывести
на печать записи с номерами от 10 до 20 включительно. Или,
допустим, вывести на печать поле номер 6 каждой записи,
начиная с той, в которой второе поле "Петр", до той, в кото-
рой пятое поле "Сидор". Для определения диапазона записей в
селекторах используется операция запятая. До запятой указы-
вается селектор, выделяющий первую запись в диапазоне, после
запятой - селектор, выделяющий последнюю запись в диапазоне.
Таким образом, мы имеем дело с составным селектором. Для
всех записей диапазона выполняется действие правила с сос-
тавным селектором.
Рассмотрим пример. Допустим, имеется следующий файл:
- 19 -
sss поле2 поле3 поле4 1
поле1 sss поле3 поле4 2
поле1 поле2 sss поле4 3
поле1 поле2 поле3 sss 4
ttt поле2 поле3 поле4 5
поле1 ttt поле3 поле4 6
поле1 поле2 ttt поле4 7
поле1 поле2 поле3 ttt 8
Допустим, необходимо вывести на печать диапазон записей.
Открывает этот диапазон запись, второе поле которой "sss", и
закрывает запись, третье поле которой "ttt". Тогда программа
выглядит следующим образом:
$2 ~ /sss/, $3 ~ /ttt/ {
print( $0 );
}
В результате выполнения получим:
поле1 sss поле3 поле4 2
поле1 поле2 sss поле4 3
поле1 поле2 поле3 sss 4
ttt поле2 поле3 поле4 5
поле1 ttt поле3 поле4 6
поле1 поле2 ttt поле4 7
В одной программе можно указать несколько правил с сос-
тавными селекторами. При этом если выделенные диапазоны
перекрываются, то каждая выделенная запись будет обрабаты-
ваться несколькими правилами. Например, для того же исход-
ного файла используется следующая программа обработки:
$2 ~ /sss/, $3 ~ /ttt/ {
print( $0 );
}
$1 ~ /sss/, NR == 5 {
print($0, "*");
}
NR == 6, NR == 8 {
print( $0, "<-" );
}
В результате выполнения получим:
- 20 -
sss поле2 поле3 поле4 1 *
поле1 sss поле3 поле4 2
поле1 sss поле3 поле4 2 *
поле1 поле2 sss поле4 3
поле1 поле2 sss поле4 3 *
поле1 поле2 поле3 sss 4
поле1 поле2 поле3 sss 4 *
ttt поле2 поле3 поле4 5
ttt поле2 поле3 поле4 5 *
поле1 ttt поле3 поле4 6
поле1 ttt поле3 поле4 6 <-
поле1 поле2 ttt поле4 7
поле1 поле2 ttt поле4 7 <-
поле1 поле2 поле3 ttt 8 <-
Чтобы устранить эффект пересечения диапазонов выделен-
ных записей, там, где это необходимо, можно использовать
оператор next. Этот оператор прекращает обработку текущей
записи, управление передается на начало программы и начина-
ется разбор следующей записи. Теперь программа будет иметь
вид:
$2 ~ /sss/, $3 ~ /ttt/ {
print( $0 );
next;
}
$1 ~ /sss/, NR == 5 {
print($0, "*");
next;
}
NR == 6, NR == 8 {
print( $0, "<-" );
}
В результате выполнения программы получим:
sss поле2 поле3 поле4 1 *
поле1 sss поле3 поле4 2
поле1 поле2 sss поле4 3
поле1 поле2 поле3 sss 4
ttt поле2 поле3 поле4 5
поле1 ttt поле3 поле4 6
поле1 поле2 ttt поле4 7
поле1 поле2 поле3 ttt 8 *
Из примера видно, что в исходном списке не нашлось ни одной
записи, которая была бы обработана всеми правилами и дейст-
вие третьего правила программы не выполнялось вообще.
- 21 -
Если в результате выполнения правила с составным селек-
тором выделено начало диапазона записей, но не выделен его
конец, действие этого правила выполняется для всех записей
до конца ввода. Если же не обнаружена запись, открывающая
диапазон записей, то действие правила с составным селектором
не выполняется.
5. Действия
Действия в правилах AWK-программы определяют алгоритм
обработки выделенных селектором записей. Для записи алго-
ритма используются присваивания, выражения, операторы управ-
ления, операторы вывода, встроенные функции.
Выше было показано, что действие в правиле записывается
как блок (в смысле языка программирования Си). Фигурная
скобка, открывающая блок, должна указываться в той же
строке, что и селектор, закрывающая - по завершению блока. В
общем случае блок может быть пустым, тогда, как это было
сказано выше, все записи, выделенные селектором, передаются
на стандартный вывод без преобразований.
К числу операторов управления относятся:
exit
завершить выполнение программы;
next
перейти к чтению следующей записи. Управление переда-
ется на первое правило AWK-программы (если имеется пра-
вило с селектором BEGIN, то на следующее за ним);
break
прерывает выполнение охватывающего цикла. Управление
передается на оператор, следующий за циклом;
continue
переход к следующей итерации цикла;
if(выражение) { блок_1 } else { блок_2 }
если значение выражения - истина, выполняются операторы
блока_1, иначе операторы блока_2. Часть else можно
опустить. Если блок_1 или блок_2 содержат по одному
оператору, фигурные скобки можно не указывать;
while(выражение) { блок }
операторы блока выполняются, пока значение выражения -
истина. Если в блоке только один оператор, фигурные
скобки можно не указывать;
for(выражение_1; выражение_2; выражение_3) { блок }
если значение выражения_2 - истина, выполняются
- 22 -
операторы блока. Выражение_1 вычисляется перед первой
итерацией цикла, выражение_3 вычисляется на каждой ите-
рации цикла. Если блок содержит один оператор, фигурные
скобки можно не указывать.
for( индекс in имя_массива ) { блок }
для каждого значения индекса массива выполняются опера-
торы блока. Значение индекса формируется автоматически
на каждой итерации цикла и равно значению, еще не
использованному в цикле. Если используется ассоциатив-
ный массив, индекс формируется в лексикографическом
порядке. Если в блоке происходит добавление элементов
массива, результат выполнения цикла непредсказуем. Если
в блоке изменяется значение индекса, результат выполне-
ния цикла непредсказуем. Вместо индекса и/или имени
массива можно указать выражение, значение которого
интерпретируется как индекс и/или имя массива.
В качестве условных выражений можно использовать любые
из описанных выше. В выражениях можно применять шаблоны,
операторы ~ и !~. Рассмотрим пример:
/aaa/ {
if( $3 !~ /fff/ )
print( $0 );
}
В записи, выделенной по селектору /aaa/, проверяется соот-
ветствие содержимого поля $3 шаблону /fff/. Если соответс-
вие не обнаружено, печатаеся вся запись, иначе оператор
print не выполняется.
Теперь рассмотрим пример использования цикла for по
идексу в ассоциативном массиве. Допустим, имеется список
записей
aaa aaa ddd ccc
ccc ddd
bbb ddd ddd
ccc
и пусть выполняется программа
- 23 -
/bbb/ { m["bbb"]++; }
/ccc/ { m["ccc"]++; }
/aaa/ { m["aaa"]++; }
/ddd/ { m["ddd"]++; }
END { for( i in m )
print("m["i"] =", m[i]);
}
В каждом из первых четырех правил селекторами выделяются
записи и подсчитывается число таких записей в ассоциативном
массиве с именем m. Цикл for выполняется по завершению
списка входных записей. В результате выполнения программы
получим:
m[aaa] = 1
m[bbb] = 1
m[ccc] = 3
m[ddd] = 3
Значением каждого элемента массива является число выделенных
селекторами записей. В результате выполнения цикла по
индексу в ассоциативном массиве получен вывод значений эле-
ментов массива в лексикографическом порядке значений
индекса.
Ниже приведен пример программы, действия которой содер-
жат примеры использования основных управляющих конструкций.
Допустим, имеется следующий текст:
aaa, aaa, aaa aaa aaa.
aaa aaa, aaa, aaa aaa.
aaa aaa aaa, aaa aaa.
aaa aaa aaa aaa, aaa.
aaa; aaa aaa aaa: aaa.
aaa aaa; aaa aaa aaa.
aaa aaa aaa; aaa; aaa.
aaa aaa: aaa aaa; aaa.
aaa: aaa aaa; aaa aaa.
aaa aaa aaa: aaa: aaa.
Требуется получить некоторую статистку о тексте:
- 24 -
# Программа вычисляет статистические
# характеристики текста.
# Разделитель записей точка.
# Разделитель полей пробел.
# Вывод результатов осуществляется
# после завершения входного текста.
BEGIN {
# выделение и инициализация
# переменных
RS = "."; # разделитель записей
Nw = 0; # число слов
Nb = 0; # число символов в словах
Np = 0; # число запятых
Nd = 0; # число двоеточий
Nt = 0; # число точек с запятой
}
{
for( i = 1; i <<= NF; i++ ){
if( $i ~ /,$/ ) {
Np++;
Nb--;
}
# Nb--; не учитывать в длине
# слова знак препинания
if( $i ~ /:$/ ) {
Nd++;
Nb--;
}
if( $i ~ /;$/ ) {
Nt++;
Nb--;
}
Nb += length( $i ); # длина слова
Nw++; # увеличить число слов
}
}
END {
print("Число запятых =", Np);
print("Число двоеточий =", Nd);
print("Число точек с запятой =", Nt);
print("Число слов =", Nw);
print("Число символов в словах =", Nb);
print("Число предложений =", NR );
print("Средняя длина предл. =", Nw/NR,"(слов)");
print("Средняя длина слова =", Nb/Nw);
- 25 -
}
Ниже показан результат работы программы:
Число запятых = 6
Чис