Документация по AWK

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

        Производственно-внедренческий кооператив
                  "И Н Т Е Р Ф Е Й С"
              Диалоговая Единая Мобильная
                  Операционная Система
                      Демос/P 2.1
                         Москва
                          1988

Аннотация

     Язык AWK  используется  для  комбинированной  обработки
символьных и числовых полей в записях.  В результате генери-
руется отчет в запланированной программистом  форме.   Прог-
раммы на языке AWK можно эффективно использовать как фильтры
данных для преобразования вывода одной программы и  передачи
результата фильтрации на вход другой.  В системе ДЕМОС уста-
новлен интерпретатор языка  AWK,  который  получил  название
awk.


                         СОДЕРЖАНИЕ


    Аннотация .........................................

1.  Принципы работы интерпретатора awk ................

2.  Переменные,  выражения  и   присваивания   в   AWK-
    программах ........................................

3.  Структура AWK-программы ...........................

4.  Селекторы .........................................

5.  Действия ..........................................

6.  Ввод и вывод данных в AWK-программах ..............

7.  Использование встроенных функций ..................




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 ); }

После выполнения программы получим:

    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-программа подсчитывает средние возраст  и
заработную  плату рабочих и служащих в списке. Для выделения
строк со сведениями о рабочих используется шаблон  /раб/,  о
служащих - шаблон /сл/.  Шаблоны содержат образцы для поиска
в полях  записи.   Данные  выводятся  после  обработки  всех
записей.

/раб/   {
    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-программа  вычисляет  уровни  заработной
платы

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-программа в файле:

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  разделитель полей записи на выводе (символ);

OFMT формат вывода чисел;

FILENAME имя входного файла (строка).


     По умолчанию имеют место следующие значения предопреде-
ленных переменных:

     RS    =   "\0";
     FS    =   'пробел(ы) и/или табуляция';
     OFS   =   FS;
     ORS   =   RS;
     OFMT  =   "%.6g";

Предопределенным переменным RS, FS,  ORS,  OFS,  OFMT  можно
присваивать значения в AWK-программе.

     В языке AWK отсутствуют декларация и явная  инициализа-
ция  переменной любого типа. Всякой переменной до ее первого
использования присваивается значение "\0" -  пустая  строка.
Применяются следующие типы переменных:

позиционная переменная;

число с плавающей точкой;

строка символов;

массив.

Позиционная переменная определяет  поле  записи,  содержимое
которого   может   быть   отнесено  к  типам   "строка"  или
"число_с_точкой" и используется в виде

    $номер_поля_записи

    $(выражение)

Номер_поля_записи может быть значением выражения.  Значением
позиционной переменной $0 является вся запись.

     Интерпретатор awk рассматривает переменную как  строко-
вую  до  того  момента, когда необходимо выполнить некоторую
операцию над ее значением. В зависимости  от  контекста  тип
значения переменной остается либо строковым, либо преобразу-
ется к типу число_с_точкой. В двусмысленных  случаях   пере-
менные  рассматриваются  как  строковые.  Строки, которые не
могут быть интерпретированы как числа, в числовом  контексте
будут  иметь  числовое значение НОЛЬ.  Устранить двусмыслен-
ность можно явным указанием типа переменной при присваивании
ей значения, например:

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("ВАЗ : ", Автомобили["ВАЗ"]);
}

Массивы можно использовать для организации такого  алгоритма
обработки  данных, в котором требуется многократный просмотр
входного потока записей.  Например,  если  не  заботиться  о
размерах  оперативной  памяти,  то  можно  весь входной файл
записать в виде массива записей  и  по  завершению  входного
потока приступить собственно к обработке:

{
     Массив_записей[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");
}

Допустим, имеется следующий входной текст:

    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 будет строка вида

    "яблоко и груша"

Вместо символа пробел можно использовать  символ  табуляции.
При  использовании  операции  "пробел" учитывается контекст,
например:

$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.

     Позиционные переменные можно использовать в  выражениях
любого вида, им можно присваивать новые значения. Рассмотрим
несколько примеров:

$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     {  действие  }

Открывающая фигурная скобка должна стоять в той  же  строке,
где селектор. В любом месте программы можно ввести коммента-
рий, он печатается от символа # до конца строки.

     Каждое правило выполняется для каждой записи  из  вход-
ного  потока. Селектор используется для того, чтобы выделить
запись, над которой будет выполнено  действие  соответствую-
щего  правила.  Если запись не выделена ни одним из селекто-
ров, она игнорируется и не выводится на  стандартный  вывод.
Если  запись выделена селектором, выполняется действие соот-
ветсвующего правила. Если  некоторую  запись  выделяют  нес-
колько селекторов, над ней выполняются действия соответству-
ющих правил.

     В правиле может отсутствовать селектор, тогда  действие
этого  правила будет выполнено для всех без исключения вход-
ных записей.  В правиле может отсутствовать действие,  тогда
все  выделенные  селектором записи будут направлены на стан-
дартный вывод без изменений.

     Определены два правила специального вида:

BEGIN         {  действие  }

      ...
список других правил
      ...

END           {  действие  }

Правило с селектором  BEGIN  выполняется  до  чтения  первой
входной  записи,  с  селектором END - после чтения последней
записи.  Правило с селектором BEGIN  должно  быть  первым  в
списке правил, с селектором END - последним.  Возможно такое
использование этих правил:

BEGIN         {  действие  }

      ...
список других правил

или

список других правил
      ...

END           {  действие  }

Действие в  правиле  может  содержать  список  операторов  и
управляющих  конструкций. Оператор должен заканчиваться сим-
волом ";", или символом новой строки, или закрывающей  скоб-
кой.

     Переменную можно  использовать  в  любом  правиле  AWK-
программы,  начиная  с места, где она определена. Рассмотрим
пример, в  котором  демонстрируются  особенности  выполнения
правил AWK-программы и использования переменных:

    # Программа   демонстрирует    работу
    # правил  различного  вида  и область
    # действия переменных

    # Правило 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

тогда после выполнения программы получим:

    Запись номер: 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 != "Москва"

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

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

    /образец/

В символах / указан образец, который будет  использован  для
поиска.   Существенно, что любой символ, в том числе пробел,
указанный внутри пары символов /, является частью образца.

     Если необходимо, чтобы соответствие  образцу  определя-
лось  в конкретном поле записи, используются операторы соот-
ветствия (~ и !~)

$номер_поля ~  шаблон
     если  при  просмотре  указанной  позиционной  перменной
     обнаруживается  цепочка символов, тождественная образцу
     в шаблоне (оператор ~), выполняется действие правила.

$номер_поля !~ шаблон
     если при просмотре указанной позиционной  перменной  не
     обнаруживается  цепочка символов, тождественная образцу
     в шаблоне (оператор !~), выполняется действие правила.


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

    /регулярное_выражение/

В результате разбора регулярного  выражения  интерпретатором
awk  строится и выполняется алгоритм поиска одного или боль-
шего числа образцов во входной записи.

     Регулярные выражения в шаблонах селекторов  AWK  анало-
гичны  подобным в lex, редакторе ed и в команде grep.  Регу-
лярное выражение формируется как композиция цепочек символов
(и/или  диапозонов символов) и операторов. Операторы в регу-
лярных выражениях указываются  в  виде  символов-операторов.
Чтобы  отнести действие символа-оператора к отдельному фраг-
менту регулярного выражения,  используются  круглые  скобки.
Чтобы  отменить  специальное значение символа-оператора, его
экранируют символом \.

     Для записи регулярных выражений употребляются следующие
символы-операторы:

^    от начала;

$    на конце;

.    любой символ;

символ
     данный символ, если он не символ-оператор;

\символ
     использовать символ-оператор как обычный символ;

[строка]
     любой из символов данной строки;

[буква1-буква2]
     любая буква из данного лексикографически упорядоченного
     диапазона букв;

[цифра1-цифра2]
     любая цифра из данного диапазона цифр;

рег_выражение*
     0 или более вхождений регулярного выражения;

рег_выражение+
     1 или более вхождений регулярного выражения;

рег_выражение?
     0 или 1 вхождение регулярного выражения;

рег_выражение1 рег_выражение2
     последовательное     вхождение     рег_выражение1     и
     рег_выражение2;

рег_выражение1|рег_выражение2
     вхождение рег_выражение1 или рег_выражение2;


     Рассмотрим несколько примеров использования  регулярных
выражений:

/^Иванов/
     выделить запись, начинающуюся цепочкой  символов  "Ива-
     нов"  Таким  образом,  будут выделены случаи типа "Ива-
     нову", "Ивановой", ... ;

$3 ~ /^Иванов/
     выделить  запись,  в  которой  третье  поле  начинается
     цепочкой символов "Иванов";

/([abc][ABC])$/
     выделить запись, предпоследним символом  которой  явля-
     ется одна из букв abc и последним - одна из букв ABC;

/[0-9]+/
     выделить запись, содержащую не менее одной цифры;

$3 !~ /(Сидор)|(Петр)/
     не выделять запись, содержащую в третьем поле  что-либо
     о Сидорах или Петрах;


     Ниже приведен пример  AWK-программы,  печатающей  имена
регистрационных  каталогов  и  имена всех пользователей сис-
темы, которыми не установлен пароль:

    BEGIN  {
            FS = ":";
            print("Имя\tКаталог");
    }

    $2 !~ /(([0-9])|([a-z])|([A-Z]))+/  {
            print( $1, "\t", $6);
    }

В первом правиле (селектор BEGIN) меняется разделитель полей
записи  с  пробела  на двоеточие (такова структура записей в
парольном файле /etc/passwd операционной системы ДЕМОС).  Во
втором поле записи парольного файла содержится зашифрованный
пароль - обычно это комбинация цифр и букв.  Если пароль  не
установлен, то второе поле записи пусто. Этот факт использо-
ван для формирования  селектора  -  второе  поле  не  должно
содержать  цифр и букв. Селектор выделяет второе поле записи
и проверяет наличие не менее одного  символа  в  этом  поле.
Если поле пусто, выполняется действие, которое заключается в
печати имени пользоватея (первое поле) и имени регистрацион-
ного каталога пользователя (шестое поле).

     Иногда  необходимо  определить  диапазон  записей,  для
которых  выполняется  действие. Например, необходимо вывести
на печать записи с номерами от 10 до 20  включительно.  Или,
допустим,  вывести  на  печать  поле  номер 6 каждой записи,
начиная с той, в которой второе поле "Петр", до той, в кото-
рой  пятое поле "Сидор". Для определения диапазона записей в
селекторах используется операция запятая. До запятой  указы-
вается селектор, выделяющий первую запись в диапазоне, после
запятой - селектор, выделяющий последнюю запись в диапазоне.
Таким  образом,  мы  имеем дело с составным селектором.  Для
всех записей диапазона выполняется действие правила  с  сос-
тавным селектором.

     Рассмотрим пример. Допустим, имеется следующий файл:

    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, "<-" );
    }

В результате выполнения получим:

    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 *

Из примера видно, что в исходном списке не нашлось ни  одной
записи,  которая была бы обработана всеми правилами и дейст-
вие третьего правила программы не выполнялось вообще.

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

5.  Действия


     Действия в правилах AWK-программы  определяют  алгоритм
обработки  выделенных  селектором  записей. Для записи алго-
ритма используются присваивания, выражения, операторы управ-
ления, операторы вывода, встроенные функции.

     Выше было показано, что действие в правиле записывается
как  блок  (в  смысле  языка  программирования Си). Фигурная
скобка,  открывающая  блок,  должна  указываться  в  той  же
строке, что и селектор, закрывающая - по завершению блока. В
общем случае блок может быть пустым,  тогда,  как  это  было
сказано  выше, все записи, выделенные селектором, передаются
на стандартный вывод без преобразований.

     К числу операторов управления относятся:

exit
     завершить выполнение программы;

next
     перейти к чтению следующей записи.  Управление  переда-
     ется на первое правило AWK-программы (если имеется пра-
     вило с селектором BEGIN, то на следующее за ним);

break
     прерывает выполнение  охватывающего  цикла.  Управление
     передается на оператор, следующий за циклом;

continue
     переход к следующей итерации цикла;

if(выражение) { блок_1 }  else { блок_2 }
     если значение выражения - истина, выполняются операторы
     блока_1,  иначе  операторы  блока_2.  Часть  else можно
     опустить. Если блок_1 или  блок_2  содержат  по  одному
     оператору, фигурные скобки можно не указывать;

while(выражение) { блок }
     операторы блока выполняются, пока значение выражения  -
     истина.   Если  в блоке только один оператор,  фигурные
     скобки можно не указывать;

for(выражение_1; выражение_2; выражение_3) { блок }
     если  значение  выражения_2   -   истина,   выполняются
     операторы  блока.  Выражение_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

и пусть выполняется программа

    /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.

Требуется получить некоторую статистку о тексте:

# Программа вычисляет статистические
# характеристики текста.
# Разделитель записей точка.
# Разделитель полей пробел.
# Вывод результатов осуществляется
# после завершения входного текста.

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);
}

Ниже показан результат работы программы:

    Число запятых = 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:

    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;
        }

      i++;


        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
равны  пустым строкам. Важно, что мы знаем точно число запи-
сей, это позволяет выделять нужные поля в  записях.   Допус-
тим, выполняется следующая командная строка:

    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:

    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

программа будет ждать ввода с клавиатуры дисплея  (завершить
ввод необходимо символом конец файла - 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

получим:

    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.  Использование встроенных функций

     Интерпретатор 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.

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