- Подписка на печатную версию:
- Подписка на электронную версию:
- Подшивки старых номеров журнала (печатные версии)
LXF138:DrBrown2
Материал из Linuxformat.
Содержание |
Awk в примерах
- Awk не обязан быть неуклюжим. Поучимся у однострочников-ветеранов.
Я немного волнуюсь, потому что наш урок будет про язык программирования. Можно ли это считать «администрированием»? Ну, вообще-то любой уважающий себя сисадмин должен обладать базовыми знаниями по программированию. Наверное, самый популярный язык программирования для администраторов – сценарии оболочки, но думаю, что Awk идет за ним по пятам. Во всяком случае, в этом месяце я номинировал Awk на звание почетного инструмента администрирования.
Скриптовый язык Awk назван по первым буквам фамилий его создателей: Альфреда Ахо [Alfred Aho], Питера Вайнбергера [Peter Weinberger] и Брайана Кернигана [Brian Kernighan]. В вашем дистрибутиве скорее всего будет GAwk (‘G’ от ‘GNU’), переписанная версия исходного Awk – надо ли говорить, что расширеннная. Awk прекрасно подходит для обработки данных, разбитых на строки, в каждой из которых есть поля, например, данные, разделенные на строки и столбцы. В Linux таких примеров множество – файлы /etc/passwd, /etc/fstab, /etc/hosts и т. д. или вывод таких команд, как df, ps -ef или ls -l, или результат экспорта электронной таблицы в файл CSV (значений, разделенных запятыми). По сути, Awk – своеобразный эквивалент электронной таблицы в командной строке. Ну, нечто вроде этого. Awk применяется для обобщения и переформатирования вывода какой-то другой программы, преобразования формата вывода программы A во входной формат программы B, проверки данных... и даже как язык программирования общего назначения.
Сравнение Awk и C
Странно, конечно, сопоставлять Мо Моулем [Mo Mowlam] и Карлу Саркози [Carla Sarkozy], но на Awk несомненно повлиял C. Их ариф метические операции и операции сравнения очень похожи. В Awk есть выражения if, else, for, break и continue, которые означают то же самое, что в C. Но в Awk нет статической типизации, как в C, и переменные не нужно объявлять до их использования; а есть встроенная проверка на соответствие регулярным выражениям, отсутствующая в C. И готовый механизм Awk «прочитать строку ввода и разбить ее на поля» в C нужно реализовывать самостоятельно.
Разбор строки за строкой
Полное описание Awk не уместится в три страницы, и я постараюсь дать вам представление об его возможностях на примерах. В классическом смысле Unix, Awk – фильтр, поскольку считывает исходный файл, если он указан, или стандартный ввод, если нет, и его можно использовать в каналах. Основная задача Awk – строка за строкой читать входные данные и разбивать каждую строку на поля.
В программе на Awk доступ к полям можно получить с помощью обозначений $1, $2 и т. д. Обозначение $0 ссылается на всю строку. Для этого не нужно писать ни строчки кода – это часть внутреннего механизма Awk. Одно из простейших применений Awk – фильтровать и переформатировать вывод других команд. Вот пример перенаправления вывода команды df в Awk:
$ df | awk ‘ /^\/dev/ { print $1 “: “ $5 }’ /dev/sda6: 56% /dev/sdb1: 10% /dev/sda3: 40%
Здесь мы велели Awk выбрать строки, начинающиеся с /dev, и вывести их первое и пятое поля.
Более длинные программы удобнее помещать в отдельный файл. В следующем примере мы отсканируем файл паролей в поисках наибольшего идентификатора пользователя. Вот программа – я назвал ее maxuid.awk:
<sourcal lang=awk>
BEGIN { FS = “:” ; maxuid = 0 } $3 > maxuid { maxuid = $3 ; maxname = $1 } END { print maxname “: “ maxuid }
</source>
В командной строке ее можно запустить, указав файл скрипта с ключом -f:
$ awk -f maxuid.Awk /etc/passwd nobody: 65534
Разберем эту программу. Как и все сценарии Awk, она состоит из пар «шаблон–действие». Шаблоны выбирают строки, над которыми нужно выполнить действия, а действия определяют, что с ними делать. Особые шаблоны BEGIN и END позволяют задать действия, выполняемые перед обработкой первой строки или после обработки последней.
Начало, середина и конец
Шаблоны BEGIN обычно используются для инициализации переменных. В данном случае, мы устанавливаем переменную разделителя поля FS в :, потому что именно так разделены поля в файле паролей. (По умолчанию в качестве разделителя используются пробелы.) Шаблон END часто используется для вывода результатов, как мы и сделали в этом примере. Средняя строка выполняет обработку. Она говорит: «Если третье поле больше, чем самый большой идентификатор, который мы видели до сих пор (maxuid), обновить значение maxuid и запомнить связанное с ним имя пользователя».
Но есть и более простой способ запустить программу. Если добавить в начало скрипта комбинацию символов #! и сделать его исполняемым, можно запустить его как команду, точно так же, как сценарий оболочки. Тогда наш скрипт будет выглядеть так:
#!/usr/bin/awk -f BEGIN { FS = “:” ; maxuid = 0 } $3 > maxuid { maxuid = $3 ; maxname = $1 } END { print maxname “: “ maxuid }
Права на выполнение можно выдать таким образом:
$ chmod u+x maxuid.Awk
Затем можно обратиться к программе по имени, как к команде:
$ ./maxuid.awk /etc/passwd nobody: 65534
Шаблон или действие в выражении Awk необязательны. Если нет шаблона, действие выполняется над каждой строкой, а если не указано действие, Awk просто выводит строку.
В очередном примере есть шаблон, но нет действия. Он показывает все учетные записи в /etc/passwd с UID 1000 или выше:
$ awk -F: ‘$3 >= 1000’ /etc/passwd
А в этом примере есть действие, но нет шаблона – он просто выводит все имена пользователей из /etc/passwd:
$ awk -F: ‘{ print $1 }’ /etc/passwd
Обратите внимание на опцию -F: она служит для указания разделителя полей. Это альтернатива явному присвоению разделителя переменной FS из наших предыдущих примеров.
Частью своей мощи Awk обязан огромному количеству вещей, допустимых в качестве шаблона, т. е. возможности выбрать строки, над которыми будет выполнено действие.
В одну строку
Как вы увидите, программам на Awk не обязательно быть длинными, чтобы быть полезными. Я бы предположил (это только догадка), что в среднестатистической программе не больше 10 строк. Вот еще несколько однострочных программ. В каждом случае приведена именно программа, а не вся командная строка.
Вывести каждую десятую строку:
NR%10 == 0
Пронумеровать строки в выводе:
{ print NR, $0 }
Вывести входные строки в обратном порядке:
{ s = $0 “\n” s } END { print s }
Вывести строки, выровненные по центру (по столбцу 40):
{ printf “%” int(40+length($0)/2) “s\n”, $0 }
Вывести непустые строки:
NF
Да, это законченная программа! Возможно, здесь нужно пояснение. NF (число полей) – встроенная переменная Awk, которая обновляется при каждой строке. Если ее использовать как шаблон (что мы и сделали), то действие по умолчанию (вывести строку) выполнится, если NF равна TRUE (не ноль).
Библиотека функций
В Awk есть маленькая, но полезная встроенная библиотека функций; при необходимости можно определить и свои собственные. Как и следовало ожидать, есть масса функций для работы со строками. Некоторые из них приведены в таблице ниже. Также есть набор математических функций, включая sin(), cos(), exp(), sqrt() и rand(); последняя представляет собой генератор случайных чисел.
Вот еще несколько программ в одну строку, использующих встроенные функции:
Вывести строки, которые длиннее 80 символов:
length($0) > 80
Этот пример меняет TTY на TERMINAL, но только в первой строке. Фрагмент NR!=1 в конце – второй шаблон, благодаря которому остальные строки выводятся без изменений:
NR==1 { gsub(“TTY”, “TERMINAL”); print } NR!=1
Обычное поведение Awk – считать каждую входную строку отдельной записью, но иногда записи бывают и в несколько строк. Например, в следующем файле содержатся две записи по три строки (имя, родной город, имя жены), разделенные пробелами:
Homer Simpson Springfield Marge
Fred Flintstone Bedrock Wilma
Чтобы обработать эти данные, нужно изменить и разделитель полей (FS), и разделитель записей (RS) в Awk. Например, следующая программа показывает жен всех жителей Бедрока. Фокус в том, что разделитель строк нужно установить в null (пустая строка):
BEGIN { RS = “” ; FS = “\n” } $2 == “Bedrock” { print $3 }
Идем вперед
Вот пример подлиннее. Вывод команды ps -ef состоит из строк следующего формата:
chris 3014 1 0 Aug17 ? 00:01:19 gedit
Седьмое поле содержит время работы процесса в формате ЧЧ:ММ:СС, и наша задача – преобразовать его в целое число секунд. Для этого его нужно разбить на три поля и снова собрать их, выполнив соответствующие арифметические действия. Вот программа на Awk: seconds.awk. Она содержит единственное действие без шаблона (поэтому оно применяется к каждой строке):
{ split($7, hms, “:”) secs = (hms[1] * 3600) + (hms[2] * 60) + hms[3] printf “%6d %5d\n”, $2, secs }
Программу можно вызвать таким образом:
$ ps -ef | awk -f seconds.awk
Ни один из приведенных примеров не продемонстрировал всей мощи Awk. Это полноценный язык со всеми необходимыми возможностями – переменными, массивами, арифметикой, циклами, ветвлением и встроенной библиотекой функций. Его набор операторов и синтаксис его циклов и ветвлений напоминают C, но без точек с запятой.
Чтобы продемонстрировать несколько большие его возможности, напишем программу, которая обрабатывает вывод команды ps, печатая информацию об общем потреблении памяти каждым пользователем, у которого есть активные процессы.
Как всегда с Awk, прежде всего нужно разобраться, как организованы данные. Вот несколько строк вывода ps:
$ ps aux USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 23820 2012 ? Ss 12:21 0:00 /sbin/init postfix 2012 0.0 0.0 39420 2224 ? S 12:22 0:00 qmgr -l -t fifo -u chris 2113 0.0 0.3 202092 13080 ? S 12:23 0:01 update-notifier chris 2182 0.2 1.4 724972 57568 ? Sl 12:35 0:20 evolution
Здесь нас интересует первое поле (имя владельца) и пятое поле (VSZ), показывающее объем виртуальной памяти процесса. Идея состоит в том, чтобы сложить все значения VSZ для пользователей chris, root и прочих, и таким образом получить сумму для каждого пользователя. Вот наша программа totmem.awk:
$1 != “USER” { count[$1]++; tot[$1] += $6 } END { for (user in tot) printf “%8s: %4d %8d\n”, user, count[user], tot[user] }
Ее вывод выглядит так:
$ ps aux | awk -f totmem.awk gdm: 1 26256 chris: 60 11502604 syslog: 1 126216 rtkit: 1 43636 daemon: 1 18880 postfix: 2 78680 nobody: 1 21424 avahi: 2 67976 root: 113 1769064
Основной элемент этой программы – два ассоциативных массива count и tot. В качестве индекса по этим массивам мы используем имя пользователя (сохраненное в $1). После обработки всех строк count[«chris»] будет содержать общее число процессов, владелец которых – пользователь chris, а tot[«chris»] – искомый объем памяти. Массивы count[«root»] и tot[«root»] будут содержать те же данные для пользователя root. Конечно, мы не знаем заранее, какие имена пользователей попадутся, но для цикла for все равно, по каким значениям проходить. Выражение printf очень похоже на вызов printf() – библиотечной функции C. Она позволяет вывести данные заданной ширины, и числа аккуратно выравниваются по столбцам.
Посчитайте суммы
Оставлю вас с программой немного в ином стиле – она не считывает никаких входных данных, а только выполняет вычисления. Она выводит все простые числа, меньшие 1000. Здесь все дело в действии для шаблона BEGIN. Это не самый эффективный алгоритм, и Awk в любом случае – не лучший выбор для работы с числами. Но пример содержит несколько циклов и ветвлений и показывает, как определять собственные функции.
BEGIN { for (x = 3; x < 1000; x += 2) if (isprime(x)) print x } function isprime(a) { for (n = 2; n*n <= a; n++) { if (a%n == 0) return 0 } return 1 }
Надеюсь, вам понравилось мое краткое введение в Awk. Теперь очередь за вами – успехов!
Где узнать больше
Классическая книга «The Awk Programming Language» [Язык программирования Awk] была написана создателями языка еще в 1988 году, но все еще издается. В книге описана исходная версия Awk, но это по-прежнему лучшее руководство по языку. В ней всего 200 страниц, но она выходит далеко за рамки простого описания языка – с главами о написании синтаксических анализаторов для «языков-малюток» и об алгоритмах вроде топологической сортировки. Также можно загрузить полное руководство по Gawk (364 страницы) в различных форматах с сайта http://www.gnu.org/software/gawk/manual. Есть еще сайт http://awk.info, где можно найти исчерпывающую подборку программ на Awk – от однострочных до действительно серьезных.