Введение в awk

  Автор: © Хосе Насарио [Jose Nazario]
Перевод: © Сергей Скороходов.


 

Аннотация

Из-за более богатого возможностями Perl часто не обращают внимания на язык программирования awk. А между прочим, awk можно найти даже на большем числе платформ, чем Perl, его легче изучить, чем Perl и он может использоваться во всех скриптах проверки состояния системы, когда эффективность имеет ключевое значение. Это краткое руководство написано для того, чтобы помочь приступить к написанию программ на awk.

Основы

Awk -- это компактный С-подобный язык, который был создан для обработки форматированного текста. Дампы баз данных и системные логи являются обычным примером. Awk весь построен на работе с регулярными выражениями и образцами, так же, как и Perl. Сказать правду, Perl -- это "правнук" awk.

Забавное имя awk (англ. awkward -- неуклюжий) происходит от имен авторов языка: Алфреда В. Ахо [Alfred V. Aho], Брайан У. Керниган [Brian W. Kernighan] и Питер Дж. Вейнбергер [Peter J. Weinberger]. Большинство из вас сразу обратили внимание на имя Кернигана, одного из "отцов" языка программирования C и влиятельнейших фигур в мире UNIX.

Использование awk для построчной обработки

Именно так я и начал использовать awk, для вывода определенных полей. Это на удивление хорошо работало, но эффективность упала до нижней отметки, когда я начал писать большие скрипты, котором требовалось для завершения несколько минут.

Но, будьте спокойны, иногда это может оказаться полезным:

ls -l /tmp/foobar | awk '{print $1"\t"$9}'

Это будет получать на входе что-то наподобие:


-rw-rw-rw-   1 root     root            1 Jul 14  1997 tmpmsg

а на выходе будет такое:


-rw-rw-rw-      tmpmsg

То, что сделал awk, интуитивно очевидно -- он напечатал первое и девятое поле. Теперь вы понимаете, почему он так популярен для построчного извлечения данных. Но давайте перейдем к полностью "оперившейся" awk-программе.

Структура программы на awk

Что я очень люблю в awk, так это удивительную читаемость кода, несмотря на сравнимые с Perl или Python возможности. Каждая awk-программа состоит из трех частей: блока BEGIN, который выполняется один раз перед чтением входных данных; гланого цикла, который выполняется при чтении каждой входной строки и блока END, который выполняется после того, как чтение входных данных закончится. Предельно ясно! Да, я непрестанно говорю это об awk, потому что это так и есть.

Вот очень простая программа, подчеркивающая некоторые особенности языка. Попробуйте сами во всем разобраться перед тем, как мы ее "препарируем":


#!/usr/bin/awk -f
#
# проверка лога su sulog...
# copyright 2001 (c) jose nazario
#
# работает на Solaris, IRIX и HPUX 10.20
BEGIN {
  print "--- проверка sulog"
  failed=0
  }
{
  if ($4 == "-") {
    print "отказано в выполнении su:\t"$6"\t дата:\t"$2"\t"$3
    failed=failed+1
    }
}
END {
  print "---------------------------------------"
  printf("\tвсего записей:\t%d\n", NR)
  printf("\tвсего отказов в выполнении su:\t%d\n",failed)
}

Еще не разобрались? Может вам поможет знание о формате строки входного файла (скажем, sulog'а от IRIX)? Вот пара типичных строк:


        SU 01/30 13:15 - ttyq1 jose-root
        SU 01/30 13:15 + ttyq1 jose-root

OK, вчитайтесь и попробуйте разобраться, как работает скрипт. Блок BEGIN делает начальные установки, выводит заголовок и инициализирует одну переменную (в данном случае счетчик failed) нулем. Затем главный цикл читает каждую входную строку (а именно sulog, лог-файла попыток выполнения su) и проверяет четвертое поле на наличие знака "-". Если он там есть, то в выполнении su было отказано: мы увеличиваем счетчик и берем на заметку, какая попытка выполнить su закончилась неудачей и когда. В конце "подбиваются бабки" и выводится число строк (как число записей NR -- внутренней переменной awk) и число обнаруженных неудачных попыток выполнить su. Вывод выглядит следующим образом:


failed su:      jose-root       at      01/30   13:15
        ---------------------------------------
        total number of records:        272
        total number of failed su's:    73

Вы также увидели, как работает printf, практически также, как и в C. Короче, awk -- довольно понятный язык.

По умолчанию поля отделяются друг от друга пробелами, но это можно "подкрутить". В файлах паролей разделителем служит двоеточие. Вот маленький скрипт, который ищет пользователей с ID равным 0 (приравненным к root) и пользователей с "пустым" паролем:


#!/usr/bin/awk -f
BEGIN { FS=":" }
{
  if ($3 == 0) print $1
  if ($2 == "") print $1
}

Другими внутренними переменными awk являются RS [record separator] для разделения записей (по умолчанию -- символ новой строки), разделитель выходных полей OFS [output field separator] (думаю, что по умолчанию он пустой) и разделитель выходных записей ORS [output record separator], по умолчанию равный символу новой строки. Естественно. что все эти переменные можно устанавливать в самом скрипте.

Регулярные выражения

Язык awk выполняет проверку обычных регулярных выражений, и делает это лучше grep. Например, я использую следующий образец поиска для проверки на наличие вероятного эксплойта на системах Intel Linux:


#!/usr/bin/awk -f
{ if ($0 ~ /\x90/) print "exploit at line " NR }

С помощью grep нельзя осуществлять поиск шестнадцатеричного значения 0x90, но 0x90 очень "популярно" в эксплойтах на платформе Intel, т.к. это код NOP, который часто служит "заполнителем" для "забивки" кода shell.

Вы можете искать шестнадцатеричные занчения с помощью \xdd, где dd обозначает шестнадцатиричное число, которое надо найти; можно искать и десятеричные значения (например коды ASCII), указывая в образце для поиска \ddd, используя десятичные значения. Текстовые регулярные выражения тоже работают, естественно.

"Случайности" в awk

В коде на awk можно генерировать случайные числа, но есть одна тонкость, которую надо иметь в виду. Функция rand() делает именно то, что вы от нее ждете, а именно возвращает случайное число, в данном случае между 0 и 1. Для получения больших значений вы можете его масштабировать. Вот пример кода, который демонстрирует использование генератора случайных числе и, заодно, показывает некоторые "интересности" поведения:

#!/usr/bin/awk -f
{
  for(i=1;i<=10;i++) 
  print rand(); exit
}

Запустите этот скрипт пару раз, и проблема станет очевидна: случайные числа не так уж и "случайны" и повторяются при каждом прогоне программы!

В чем проблема? Мы не дали "затравку" [seed] для генератора случайных чисел. Обычно как "элемент случайности" используется такой хороший источник, как /dev/random (в Linux). Однако, awk этого не делает. Для получения действительно случайных чисел, надо задать генератору случайных чисел "затравку" [seed]. Исправленный код выглядит так:

#!/usr/bin/awk -f
BEGIN {
  srand()
}
{
  for(i=1;i<=10;i++)
  print rand(); exit
}

Весь фокус в "запитывании" [seeding] генератора случайных чисел в блоке BEGIN. Функция srand() может получать один аргумент, а при отсутствии такового в качестве "затравки" берутся текущие время и дата. Обратите внимание, что одинаковая "затравка" всегда порождает одну и ту же "случайную" последовательность.

Заключение

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

Мы даже не коснулись массивов, самостроящихся функций и других сложных средств языка, но достаточно, чтобы сказать, что awk вряд ли можно назвать "младшим братишкой" Perl.

Вперед и с песней!

Материалы

На домашней странице Кернигана находится список хороших книг по awk и исходные тексты для "настоящего awk", он же "nawk". Там же можно обнаружить кучу интересных ссылок и пояснения лично от Кернигана.

http://cm.bell-labs.com/who/bwk/

Стандартная реализация awk, nawk (от new awk, в противоположность "старому awk", который иногда для совместимости называют 'oawk') основана на определениях awk в POSIX и содержит несколько функций из двух других реализаций: gawk и mawk. Я обычно держу эту реализацию под рукой под именем nawk и использую для проверки переносимости моих скриптов. С ним же я обычно сталкиваюсь на коммерческом UNIX, где часто не устанавливается gawk.

Исходные тексты nawk: http://cm.bell-labs.com/who/bwk/awk.tar.gz

Awk из проекта GNU, gawk, тоже базируется на POSIX стандарте awk, но добавляет значительное число полезных средств. Это включает параметры командной строки вроде провеки через 'lint' и восстановления режима строгой совместимостью с POSIX. Моими любимыми чертами gawk являются разрыв строки с помощью '\' и расширенные регулярные выражения. Подробное обсуждение расширений GNU содержится в документации. Именно gawk используется в качестве стандартного 'awk' в Linux и BSD.

Исходные тексты gawk: ftp://gnudist.gnu.org/gnu/gawk/gawk-3.0.6.tar.gz (версия awk в GNU Project)

А вот, наверное, самая популярная книга об этих двух маленьких программах, она котируется очень высоко. Помимо всего остального, в ней есть подробное обсуждение популярных реализаций awk (gawk, nawk, mawk и т.д.), прекрасная подборка функций, и оно прекрасно читается, как всегда у книг O'Reilly. На домашней странице awk есть список других книг по awk, но это -- моя любимая.

Книга по sed & awk: http://www.oreilly.com/catalog/sed2

Хосе Насарио

Хосе -- аспирант-биохимик в Case Wester Reserve Univercity в Кливленде, штат Огайо. С UNIX работает почти десять лет, а с Linux начиная с ядра 1.2.

 


Copyright © 2001, Jose Nazario.
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 67 of Linux Gazette, June 2001

Вернуться на главную страницу