Страница:
допустим, вывести на печать поле номер 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
Число двоеточий = 5
Число точек с запятой = 6
Число слов = 50
Число символов в словах = 150
Число предложений = 10
Средняя длина предл. = 5 (слов)
Средняя длина слова = 3
6. Ввод и вывод данных в AWK-программах
Ввод данных в AWK-программу определяется именем вход-
ного файла в командной строке. Таких файлов может быть нес-
колько, и обрабатываться AWK-программой они будут последова-
тельно в том порядке, в котором указаны в командной строке,
например:
awk -f prog f1 f2 f3 f4
AWK-программа из файла prog будет выполняться над входным
потоком записей из файлов f1, f2, f3 и f4. Здесь необходимо
отметить, что предопределенная переменная NR будет иметь
значение, равное порядковому номеру записи ( NR не обнуля-
ется при переходе к чтению очередного файла). Пусть имеются
четыре файла. Файл f1:
a[1][1] a[1][2] a[1][3] a[1][4]
a[2][1] a[2][2] a[2][3] a[2][4]
a[3][1] a[3][2] a[3][3] a[3][4]
a[4][1] a[4][2] a[4][3] a[4][4]
Файл f2:
b[1][1] b[1][2] b[1][3] b[1][4]
b[2][1] b[2][2] b[2][3] b[2][4]
b[3][1] b[3][2] b[3][3] b[3][4]
b[4][1] b[4][2] b[4][3] b[4][4]
Файл f3:
c[1][1] c[1][2] c[1][3] c[1][4]
c[2][1] c[2][2] c[2][3] c[2][4]
c[3][1] c[3][2] c[3][3] c[3][4]
c[4][1] c[4][2] c[4][3] c[4][4]
Файл f4:
- 26 -
d[1][1] d[1][2] d[1][3] d[1][4]
d[2][1] d[2][2] d[2][3] d[2][4]
d[3][1] d[3][2] d[3][3] d[3][4]
d[4][1] d[4][2] d[4][3] d[4][4]
Каждый из этих файлов включает по четыре записи (по четыре
поля в каждой). Другими словами, каждый файл - матрица
(4*4). Допустим, необходимо получить новую матрицу с раз-
мерностью (4*4), столбцы которой составлены из элементов
диагоналей исходных матриц. Ниже приведен текст программы, в
которой решается эта задача:
{
if( FILENAME != Name ) {
i = 0;
Name = FILENAME;
}
if( i == 1 ) {
Dig1 = Dig1 " " $1;
next;
}
if( i == 2 ) {
Dig2 = Dig2 " " $2;
next;
}
if( i == 3 ) {
Dig3 = Dig3 " " $3;
next;
}
if( i == 4 ) Dig4 = Dig4 " " $4;
}
END {
print( Dig1 );
print( Dig2 );
print( Dig3 );
print( Dig4 );
}
В программе два правила. Первое правило не содержит
селектора, следовательно, выполняется для всех входных запи-
сей. Второе правило выполняется по завершению входного
потока. Программа работает следующим образом: первоначально
проверяется, изменилось ли имя входного файла (предопреде-
ленная переменная FILENAME), затем, если не изменилось,
присваивается значение соответствующего поля записи к пере-
менной Dig (используется операция конкатенации старого зна-
чения Dig со значением поля и присваивания Dig нового значе-
ния). Переменная Name предназначена для сохранения имени
входного файла. Первоначально значения переменных Name и Dig
- 27 -
равны пустым строкам. Важно, что мы знаем точно число запи-
сей, это позволяет выделять нужные поля в записях. Допус-
тим, выполняется следующая командная строка:
awk -f prog f1 f2 f3 f4 >> Result
в файле Result будем иметь:
a[1][1] b[1][1] c[1][1] d[1][1]
a[2][2] b[2][2] c[2][2] d[2][2]
a[3][3] b[3][3] c[3][3] d[3][3]
a[4][4] b[4][4] c[4][4] d[4][4]
Результат работы программы существенно связан с порядком
чтения входных файлов. Если выполнить командную строку
awk -f prog f4 f3 f2 f1 >> Result
получим:
d[1][1] c[1][1] b[1][1] a[1][1]
d[2][2] c[2][2] b[2][2] a[2][2]
d[3][3] c[3][3] b[3][3] a[3][3]
d[4][4] c[4][4] b[4][4] a[4][4]
Когда возникает необходимость передать в AWK-программу
значения некоторых переменных, можно воспользоваться возмож-
ностью указать их в файле. Допустим, заранее не известны
образцы для выделения записей файла f1. В этом случае можно
создать файл f0 с описаниями образцов и, воспользовавшись
значением переменной FILENAME, присвоить этим переменным
нужные значения. Пусть файл f0 имеет вид:
aaa bbb ccc
Пусть файл f1 имеет вид:
aaa bbb ccc ddd eee
eee bbb ccc ddd aaa
aaa fff ccc ddd eee
aaa bbb ggg ttt eee
Программа на AWK:
- 28 -
FILENAME == "f0" {
pat1 = $1;
pat2 = $2;
pat3 = $3;
next;
}
$1 == pat1 { print; next }
$2 == pat2 { print; next }
$3 == pat3 { print }
После выполнения командной строки
awk -f prog f0 f1
получим в файле Result:
aaa bbb ccc ddd eee
aaa fff ccc ddd eee
aaa bbb ggg ttt eee
Можно предусмотреть ввод переменных со стандартного
ввода; воспользуемся тем, что переменная FILENAME для стан-
дартного ввода определена как "-". Пусть файл f1 имеет вид:
aaa bbb ccc ddd eee
eee bbb ooo ddd aaa
aaa fff ccc ddd eee
qqq bbb ggg ttt eee
ooo fff ggg ttt eee
ccc bbb ggg ttt eee
Приведенная ниже программа позволяет получить значения пере-
менных с клавиатуры дисплея:
BEGIN { print("Вводите значения полей:"); }
FILENAME == "-" {
pat1 = $1;
pat2 = $2;
pat3 = $3;
}
FILENAME == "f1" {
if($1 == pat1) { print($0); next }
if($2 == pat2) { print($0); next }
if($3 == pat3) { print($0);}
}
После запуска на выполнение следующей командной строки
awk -f prog - f1
- 29 -
программа будет ждать ввода с клавиатуры дисплея (завершить
ввод необходимо символом конец файла - CTRL/D). Например:
Вводите значения полей:
qqq fff ooo
CTRL/D
eee bbb ooo ddd aaa
aaa fff ccc ddd eee
qqq bbb ggg ttt eee
ooo fff ggg ttt eee
Как уже говорилось раньше, вывод AWK-программы направ-
ляется на экран дисплея, если не было указано другое.
Существует возможность направить вывод по нескольким каналам
непосредственно из AWK-программы, для этого можно воспользо-
ваться стандартными средствами системы ДЕМОС. Например:
print( $0 ) >> "file";
запись будет направлена в файл с именем ./file;
print( $0 ) >>>> "file";
запись будет дописана в ./file;
print( $0 ) >> $2;
запись будет направлена в файл с именем, равным содержимому
ее второго поля.
Существует возможность из AWK-программы направить вывод
в конвейер, например:
{
print($0) | "tr ' ' '\n' | sort ";
}
Здесь запись будет направлена команде tr, которая заменит
пробел символом '\n', затем отсортирована командой sort.
Пусть выполнена следующая командная строка:
awk -f prog -
после ввода с клавиатуры нескольких записей
dfa nrk klm njf rty xvz
saa ass dcf vfr klm ttr
CTRL/D
получим:
- 30 -
ass
dcf
dfa
klm
klm
njf
nrk
rty
saa
ttr
vfr
xvz
Вывод результата работы конвейера осуществляется по заверше-
нию чтения последней входной записи. Канал вывода в примере
совпадает с каналом стандартного вывода, но его можно пере-
определить на любой файл.
В одной AWK-программе можно одновременно определить
несколько каналов вывода, число которых зависит от числа
файлов, разрешенных для одновременного использования. Это
число устанавливается при генерации операционной системы
ДЕМОС.
Для вывода данных в AWK-программе предназначен оператор
print. До настоящего момента мы применяли лишь одну форму
использования этого оператора:
print(список_фактических_параметров);
Круглые скобки использовались раньше для того, чтобы не отв-
лекать читателя, знакомого с языком программирования Си, -
их можно не указывать. Существуют и другие формы использова-
ния этого оператора:
print;
выводится вся запись;
print $1, $2;
значения полей выводятся через пробел;
print $1 $2;
выводится конкатенация значений полей.
При необходимости управления форматом вывода можно
использовать библиотечную функцию printf, синтаксис и
результат работы которой такие же, как и в языке Си.
7. Использование встроенных функций
- 31 -
Интерпретатор awk включает набор встроенных функций,
которые можно использовать в действиях правил. Существуют
два способа вызова встроенных функций:
имя_функции(список_фактических_параметров)
имя_функции
Во втором случае в качестве фактического параметра применя-
ется вся текущая запись. Как обычно, значение функции подс-
тавляется в выражение в том месте, где определен вызов.
Имеются следующие встроенные функции:
length(выражение)
значением выражения является строка. Функция length
возвращает длину строки, например:
print( length($1 " " $2));
будет напечатана длина строки, полученной конкатенацией
поля $1, пробела и поля $2. Форма без аргумента возв-
ращает длину записи.
exp(выражение)
возвращает экспоненту от выражения.
log(выражение)
возвращает натуральный логарифм выражения.
sqrt(выражение)
возвращает значение квадратного корня от выражения.
int(выражение)
возвращает целую часть числа, равного значению выраже-
ния.
substr(S, M, N)
возвращает часть строки S, начинающуюся от позиции M и
имеющую длину не более N символов. Символы в строке S
нумеруются с 1. Если аргумент N не указан, возвраща-
ются все символы от M до конца строки.
string = substr( $0, 12, 20);
String будет включать 9 символов (с 12 по 20) текущей
записи.
index(As, Ps)
возвращает номер позиции, с которой строка Ps совпадает
со строкой As. Если совпадения нет, возвращается 0.
- 32 -
sprintf(формат, выражение, ...)
возвращает строку, выведенную по формату. Синтаксис
функции и результат работы аналогичны функции sprintf в
библиотеке языка программирования Си.
split( S, Name, разделитель )
строка S разбивается на поля, значения которых присваи-
ваются элементам массива Name. Значением первого эле-
мента Name[1] будет содержимое первого выделенного
поля, значением второго элемента Name[2] - второго
выделенного поля и так далее. Если не указан раздели-
тель полей, используется значение предопределенной
переменной FS. Функция split возвращает число выделен-
ных полей. Рассмотрим пример. Пусть имеется файл f1
aaa bbb ccc# ddd# eee fff# ggg
ttt# ggg eee# ccc ddd sss# yyy
и AWK-программа
{
i = split( $0, Name, "#");
for(j = 1; j <<= i; j++)
print( "Name["j"] =", Name[j]);
}
после выполнения командной строки
awk -f prog f1
получим:
Name[1] = aaa bbb ccc
Name[2] = ddd
Name[3] = eee fff
Name[4] = ggg
Name[1] = ttt
Name[2] = ggg eee
Name[3] = ccc ddd sss
Name[4] = yyy
- 33 -
СОДЕРЖАНИЕ
Аннотация ......................................... 2
1. Принципы работы интерпретатора awk ................ 3
2. Переменные, выражения и присваивания в AWK-
программах ........................................ 7
3. Структура AWK-программы ........................... 13
4. Селекторы ......................................... 16
5. Действия .......................................... 22
6. Ввод и вывод данных в AWK-программах .............. 26
7. Использование встроенных функций .................. 31
- 34 -
начиная с той, в которой второе поле "Петр", до той, в кото-
рой пятое поле "Сидор". Для определения диапазона записей в
селекторах используется операция запятая. До запятой указы-
вается селектор, выделяющий первую запись в диапазоне, после
запятой - селектор, выделяющий последнюю запись в диапазоне.
Таким образом, мы имеем дело с составным селектором. Для
всех записей диапазона выполняется действие правила с сос-
тавным селектором.
Рассмотрим пример. Допустим, имеется следующий файл:
- 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
Число двоеточий = 5
Число точек с запятой = 6
Число слов = 50
Число символов в словах = 150
Число предложений = 10
Средняя длина предл. = 5 (слов)
Средняя длина слова = 3
6. Ввод и вывод данных в AWK-программах
Ввод данных в AWK-программу определяется именем вход-
ного файла в командной строке. Таких файлов может быть нес-
колько, и обрабатываться AWK-программой они будут последова-
тельно в том порядке, в котором указаны в командной строке,
например:
awk -f prog f1 f2 f3 f4
AWK-программа из файла prog будет выполняться над входным
потоком записей из файлов f1, f2, f3 и f4. Здесь необходимо
отметить, что предопределенная переменная NR будет иметь
значение, равное порядковому номеру записи ( NR не обнуля-
ется при переходе к чтению очередного файла). Пусть имеются
четыре файла. Файл f1:
a[1][1] a[1][2] a[1][3] a[1][4]
a[2][1] a[2][2] a[2][3] a[2][4]
a[3][1] a[3][2] a[3][3] a[3][4]
a[4][1] a[4][2] a[4][3] a[4][4]
Файл f2:
b[1][1] b[1][2] b[1][3] b[1][4]
b[2][1] b[2][2] b[2][3] b[2][4]
b[3][1] b[3][2] b[3][3] b[3][4]
b[4][1] b[4][2] b[4][3] b[4][4]
Файл f3:
c[1][1] c[1][2] c[1][3] c[1][4]
c[2][1] c[2][2] c[2][3] c[2][4]
c[3][1] c[3][2] c[3][3] c[3][4]
c[4][1] c[4][2] c[4][3] c[4][4]
Файл f4:
- 26 -
d[1][1] d[1][2] d[1][3] d[1][4]
d[2][1] d[2][2] d[2][3] d[2][4]
d[3][1] d[3][2] d[3][3] d[3][4]
d[4][1] d[4][2] d[4][3] d[4][4]
Каждый из этих файлов включает по четыре записи (по четыре
поля в каждой). Другими словами, каждый файл - матрица
(4*4). Допустим, необходимо получить новую матрицу с раз-
мерностью (4*4), столбцы которой составлены из элементов
диагоналей исходных матриц. Ниже приведен текст программы, в
которой решается эта задача:
{
if( FILENAME != Name ) {
i = 0;
Name = FILENAME;
}
if( i == 1 ) {
Dig1 = Dig1 " " $1;
next;
}
if( i == 2 ) {
Dig2 = Dig2 " " $2;
next;
}
if( i == 3 ) {
Dig3 = Dig3 " " $3;
next;
}
if( i == 4 ) Dig4 = Dig4 " " $4;
}
END {
print( Dig1 );
print( Dig2 );
print( Dig3 );
print( Dig4 );
}
В программе два правила. Первое правило не содержит
селектора, следовательно, выполняется для всех входных запи-
сей. Второе правило выполняется по завершению входного
потока. Программа работает следующим образом: первоначально
проверяется, изменилось ли имя входного файла (предопреде-
ленная переменная FILENAME), затем, если не изменилось,
присваивается значение соответствующего поля записи к пере-
менной Dig (используется операция конкатенации старого зна-
чения Dig со значением поля и присваивания Dig нового значе-
ния). Переменная Name предназначена для сохранения имени
входного файла. Первоначально значения переменных Name и Dig
- 27 -
равны пустым строкам. Важно, что мы знаем точно число запи-
сей, это позволяет выделять нужные поля в записях. Допус-
тим, выполняется следующая командная строка:
awk -f prog f1 f2 f3 f4 >> Result
в файле Result будем иметь:
a[1][1] b[1][1] c[1][1] d[1][1]
a[2][2] b[2][2] c[2][2] d[2][2]
a[3][3] b[3][3] c[3][3] d[3][3]
a[4][4] b[4][4] c[4][4] d[4][4]
Результат работы программы существенно связан с порядком
чтения входных файлов. Если выполнить командную строку
awk -f prog f4 f3 f2 f1 >> Result
получим:
d[1][1] c[1][1] b[1][1] a[1][1]
d[2][2] c[2][2] b[2][2] a[2][2]
d[3][3] c[3][3] b[3][3] a[3][3]
d[4][4] c[4][4] b[4][4] a[4][4]
Когда возникает необходимость передать в AWK-программу
значения некоторых переменных, можно воспользоваться возмож-
ностью указать их в файле. Допустим, заранее не известны
образцы для выделения записей файла f1. В этом случае можно
создать файл f0 с описаниями образцов и, воспользовавшись
значением переменной FILENAME, присвоить этим переменным
нужные значения. Пусть файл f0 имеет вид:
aaa bbb ccc
Пусть файл f1 имеет вид:
aaa bbb ccc ddd eee
eee bbb ccc ddd aaa
aaa fff ccc ddd eee
aaa bbb ggg ttt eee
Программа на AWK:
- 28 -
FILENAME == "f0" {
pat1 = $1;
pat2 = $2;
pat3 = $3;
next;
}
$1 == pat1 { print; next }
$2 == pat2 { print; next }
$3 == pat3 { print }
После выполнения командной строки
awk -f prog f0 f1
получим в файле Result:
aaa bbb ccc ddd eee
aaa fff ccc ddd eee
aaa bbb ggg ttt eee
Можно предусмотреть ввод переменных со стандартного
ввода; воспользуемся тем, что переменная FILENAME для стан-
дартного ввода определена как "-". Пусть файл f1 имеет вид:
aaa bbb ccc ddd eee
eee bbb ooo ddd aaa
aaa fff ccc ddd eee
qqq bbb ggg ttt eee
ooo fff ggg ttt eee
ccc bbb ggg ttt eee
Приведенная ниже программа позволяет получить значения пере-
менных с клавиатуры дисплея:
BEGIN { print("Вводите значения полей:"); }
FILENAME == "-" {
pat1 = $1;
pat2 = $2;
pat3 = $3;
}
FILENAME == "f1" {
if($1 == pat1) { print($0); next }
if($2 == pat2) { print($0); next }
if($3 == pat3) { print($0);}
}
После запуска на выполнение следующей командной строки
awk -f prog - f1
- 29 -
программа будет ждать ввода с клавиатуры дисплея (завершить
ввод необходимо символом конец файла - CTRL/D). Например:
Вводите значения полей:
qqq fff ooo
CTRL/D
eee bbb ooo ddd aaa
aaa fff ccc ddd eee
qqq bbb ggg ttt eee
ooo fff ggg ttt eee
Как уже говорилось раньше, вывод AWK-программы направ-
ляется на экран дисплея, если не было указано другое.
Существует возможность направить вывод по нескольким каналам
непосредственно из AWK-программы, для этого можно воспользо-
ваться стандартными средствами системы ДЕМОС. Например:
print( $0 ) >> "file";
запись будет направлена в файл с именем ./file;
print( $0 ) >>>> "file";
запись будет дописана в ./file;
print( $0 ) >> $2;
запись будет направлена в файл с именем, равным содержимому
ее второго поля.
Существует возможность из AWK-программы направить вывод
в конвейер, например:
{
print($0) | "tr ' ' '\n' | sort ";
}
Здесь запись будет направлена команде tr, которая заменит
пробел символом '\n', затем отсортирована командой sort.
Пусть выполнена следующая командная строка:
awk -f prog -
после ввода с клавиатуры нескольких записей
dfa nrk klm njf rty xvz
saa ass dcf vfr klm ttr
CTRL/D
получим:
- 30 -
ass
dcf
dfa
klm
klm
njf
nrk
rty
saa
ttr
vfr
xvz
Вывод результата работы конвейера осуществляется по заверше-
нию чтения последней входной записи. Канал вывода в примере
совпадает с каналом стандартного вывода, но его можно пере-
определить на любой файл.
В одной AWK-программе можно одновременно определить
несколько каналов вывода, число которых зависит от числа
файлов, разрешенных для одновременного использования. Это
число устанавливается при генерации операционной системы
ДЕМОС.
Для вывода данных в AWK-программе предназначен оператор
print. До настоящего момента мы применяли лишь одну форму
использования этого оператора:
print(список_фактических_параметров);
Круглые скобки использовались раньше для того, чтобы не отв-
лекать читателя, знакомого с языком программирования Си, -
их можно не указывать. Существуют и другие формы использова-
ния этого оператора:
print;
выводится вся запись;
print $1, $2;
значения полей выводятся через пробел;
print $1 $2;
выводится конкатенация значений полей.
При необходимости управления форматом вывода можно
использовать библиотечную функцию printf, синтаксис и
результат работы которой такие же, как и в языке Си.
7. Использование встроенных функций
- 31 -
Интерпретатор awk включает набор встроенных функций,
которые можно использовать в действиях правил. Существуют
два способа вызова встроенных функций:
имя_функции(список_фактических_параметров)
имя_функции
Во втором случае в качестве фактического параметра применя-
ется вся текущая запись. Как обычно, значение функции подс-
тавляется в выражение в том месте, где определен вызов.
Имеются следующие встроенные функции:
length(выражение)
значением выражения является строка. Функция length
возвращает длину строки, например:
print( length($1 " " $2));
будет напечатана длина строки, полученной конкатенацией
поля $1, пробела и поля $2. Форма без аргумента возв-
ращает длину записи.
exp(выражение)
возвращает экспоненту от выражения.
log(выражение)
возвращает натуральный логарифм выражения.
sqrt(выражение)
возвращает значение квадратного корня от выражения.
int(выражение)
возвращает целую часть числа, равного значению выраже-
ния.
substr(S, M, N)
возвращает часть строки S, начинающуюся от позиции M и
имеющую длину не более N символов. Символы в строке S
нумеруются с 1. Если аргумент N не указан, возвраща-
ются все символы от M до конца строки.
string = substr( $0, 12, 20);
String будет включать 9 символов (с 12 по 20) текущей
записи.
index(As, Ps)
возвращает номер позиции, с которой строка Ps совпадает
со строкой As. Если совпадения нет, возвращается 0.
- 32 -
sprintf(формат, выражение, ...)
возвращает строку, выведенную по формату. Синтаксис
функции и результат работы аналогичны функции sprintf в
библиотеке языка программирования Си.
split( S, Name, разделитель )
строка S разбивается на поля, значения которых присваи-
ваются элементам массива Name. Значением первого эле-
мента Name[1] будет содержимое первого выделенного
поля, значением второго элемента Name[2] - второго
выделенного поля и так далее. Если не указан раздели-
тель полей, используется значение предопределенной
переменной FS. Функция split возвращает число выделен-
ных полей. Рассмотрим пример. Пусть имеется файл f1
aaa bbb ccc# ddd# eee fff# ggg
ttt# ggg eee# ccc ddd sss# yyy
и AWK-программа
{
i = split( $0, Name, "#");
for(j = 1; j <<= i; j++)
print( "Name["j"] =", Name[j]);
}
после выполнения командной строки
awk -f prog f1
получим:
Name[1] = aaa bbb ccc
Name[2] = ddd
Name[3] = eee fff
Name[4] = ggg
Name[1] = ttt
Name[2] = ggg eee
Name[3] = ccc ddd sss
Name[4] = yyy
- 33 -
СОДЕРЖАНИЕ
Аннотация ......................................... 2
1. Принципы работы интерпретатора awk ................ 3
2. Переменные, выражения и присваивания в AWK-
программах ........................................ 7
3. Структура AWK-программы ........................... 13
4. Селекторы ......................................... 16
5. Действия .......................................... 22
6. Ввод и вывод данных в AWK-программах .............. 26
7. Использование встроенных функций .................. 31
- 34 -