включить редактирование

Язык awk

Создан:

Для начала разберемся со странным названием. Awk назван по фамилиям троих его изобретателей — Альфреда Ахо [Alfred Aho], Питера Вайнбергера [Peter Weinberger] и Брайана Кернигана [Brian Kernighan]. Для меня Awk — это квинтэссенция Linux. Почему? Ну, это текстовый фильтр, и им исключительно удобно пользоваться в каналах, но главное — в нем можно делать реально полезные вещи всего несколькими строками кода.
Awk — полноценный язык программирования. В нем есть переменные, арифметические операции, массивы, циклы, ветвление, функции и все прочее, что языку программирования и полагается. Но здесь я не буду пытаться составить систематическое описание языка; моя цель — показать и объяснить несколько примеров того, как можно делать полезные вещи одной-двумя строками кода. Торжество тезиса «Прекрасное в малом», если хотите.
В Awk есть масса встроенных функций. Есть математические функции, такие как sin(), cos(), log() и sqrt(), которые вам возможно пригодятся, и функции обработки строк, которые могут оказаться наиболее более полезными, от length!.), просто возвращающей длину строки, до split(), разделяющей строку на фрагменты, и gsub(), выполняющей замену текста по регулярному выражению. Можно определить и собственные функции.
Основной инстинкт
Основной инстинкт Awk — считывать входной поток строка за строкой и разбирать строку на поля. NF-количество полей, NR - количество строк. В программе Awk для обращения к полям используются обозначения $1, $2 и т.д. По умолчанию поля разделяются пробелами, но это можно изменить, в чем мы вскоре убедимся. В примере показан структурированный «список» и его разбор на поля.

$1        $2        $3      $4
name1 text_12 text_13 text_14
name2 text_22 text_23 text_24
name3 text_32 text_33 text_34
name4 text_42 text_43 text_44
Программа на Awk состоит из набора шаблонов [patterns] и действий [actions]:
Шаблон выбирает строки для обработки, а действие описывает, что будет сделано со строками, соответствующими шаблону. Так как пока мы не знаем, на что похожи шаблон и действие, смысла в этой информации немного, но вот простой пример. Эта команда Awk покажет точки монтирования (второе поле) для всех строк в /etc/fstab: 
$awk '{print $2}' /etc/fstab

Здесь '{print $2}' — действие (печать второго поля). Где же шаблон, спросите вы? Хороший вопрос! Его нет, и в данном случае (весьма распространенном) Awk будет выполнять действие над каждой строкой.
Если вы попробуете выполнить команду со своим fstab, то скорее всего получите некорректный результат из-за строк комментариев. Строки комментария начинаются с #, и с помощью шаблона (в данном случае, регулярного выражения, обрамленного слэшами) можно отфильтровать эти строки таким образом: 
$ awk '/^[^#]/{ print $2}' /etc/fstab

Если вы не владеете языком регулярных выражений, поясню, что регулярное выражение (между слэшами) говорит «строки, которые начинаются с любого символа, кроме #». Вот еще один пример выбора одного поля. Вывод команды date имеет следующую структуру:
Mon Nov 4 10:10:41 BST2013 и мы можем вывести поле со временем так: 
$ date | awk'{print $4}'

Здесь важно отметить, что Awk обрабатывает поток данных из канала, а не файл. (В данном случае в потоке всего одна строка — в примерах Awk это бывает довольно часто). Отметим очевидный (но важный) момент: чтобы обрабатывать текст с Awk, нужно понимать структуру данных, в частности, какие данные содержатся в каждом поле.
В Linux есть много структурированных текстовых файлов, где в качестве разделителя используется : — на ум приходит /etc/ passwd — поэтому иногда нам нужно указать Awk на использование другого разделителя, как в этом примере, в котором выводятся только имена пользователей (первое поле) из файла паролей: 
$ awk -F:'{ print $1 }' /etc/passwd

Обратите внимание на использование параметра -F для указания разделителя полей. (Существуют и другие способы разделения полей, в том числе поля с фиксированной длиной и более хитрые методы на основе регулярных выражений.)
Сделаем еще шаг вперед и выведем только имена пользователей, соответствующие учетным записям обычных пользователей. В Red Hat их можно определить по универсальному идентификатору (третье поле), он должен быть больше или равен 500, и выбрать их в шаблоне Awk можно так: 
$ awk -F: '$3 >= 500 {print $1}' /etc/passwd

Этот с виду невинный пример на самом деле дает первый намек на истинную мощь Awk: он способен выполнять арифметические операции, что не по зубам средствам вроде grep и sed.
Вот еще одна однострочная программа, выполняющая арифметические действия. Она показывает среднее для чисел, введенных в строке:
$ awk '{sum=0; for (i=1; k=NF; i++) sum += $i; print sum/NF; }'

Я не указал входной файл: как настоящий фильтр, Awk берет данные с клавиатуры, и я ввожу их вручную. Посмотрите внимательно, и вы увидите, что строки вывода чередуются со строками ввода. Для обработки каждого поля в программе используется классический цикл for в стиле языка С, в котором числа суммируются. Во встроенную переменную NF записывается количество полей в текущей строке ввода. Помните, что в этой программе шаблон (PATTERN) не задается, поэтому она выполняется над всеми входными строками.
Awk в реальном мире
Awk находит широкое применение в скриптах системного администрирования Linux, и большинство из них — всего лишь однострочные. Вот один такой, из файла /etc/init/mounted-tmp. conf в моей Ubuntu 12.04. Я сохранил окружающие строки, чтобы вы могли увидеть контекст:
# Проверим, хватит ли места в /tmp, и если нет, смонтируем tmpfs там
avail=`df -kP /tmp | awk 'NR==2 {print $4 }'` if [ "$avail" -lt 1000 ]; then mount -t tmpfs -o size=1048576,mode=1777 overflow /tmp
fi

В этом классическом примере Awk появляется в подстановке команды, чтобы задать значение переменной скрипта. Здесь Awk выбирает четвертое поле второй строки вывода команды df. Это не произвольный выбор — тот, кто писал скрипт, точно знал, каким будет вывод команды df.
Программы длиннее одной строки
Наши программы на Awk становятся длиннее, и набирать их в одной строке определенно все утомительнее; поэтому, прежде чем двигаться дальше, посмотрим, как поместить их во внешний файл скрипта. Это просто: поместите свои команды в файл и сошлитесь на него в командной строке с параметром -f. Вот программа, которая находит самый большой универсальный идентификатор в файле паролей:
BEGIN { maxuid = 0; FS =":" }
{if ($3>maxuid) maxuid = $3}
END { print "the largest UID is", maxuid }

Если поместить эти строки в файл maxuid, то его можно будет запустить так:
$ awk -f maxuid /etc/passwd

Разберем эту программу. В ней три выражения. Специальный шаблон BEGIN применяется перед обработкой первой строки. Здесь мы используем его для инициализации парочки переменных, в том числе встроенной переменной FS, которая задает разделитель полей. Это альтернатива параметру -F в командной строке. Иногда шаблон BEGIN также используется для печати заголовков. У второго выражения нет шаблона, поэтому действие выполняется с каждой строкой. Это логика, отслеживающая самый большой UID (в третьем поле). Наконец, шаблон END означает, что мы обработали последнюю строку, и часто применяется для вывода окончательных результатов, как мы поступаем и здесь.

ПеременнаяЗначение
ARGC, ARGVПредоставляют доступ к аргументам командной строки, переданным Awk.
NFКоличество полей в текущей строке.
NRНомер текущей записи (строки).
ENVIRONАссоциативный массив, предоставляющий доступ к переменным окружения программы.
FSРазделитель полей входного потока (по умолчанию — пробел).
RSРазделитель строк (по умолчанию — символ перехода на новую строку, но может использоваться регулярное выражение).
OFSРазделитель полей выходного потока — используется для разделения полей, выводимых оператором print. По умолчанию — пробел.
IGNORECASEЕсли параметр установлен, проверка регулярных выражений в Awk будет осуществляться без учета регистра.
FIELDWIDTHSРазделенный запятыми список ширины полей для разделения входного потока на колонки фиксированной ширины.

Обратите внимание, что не нужно предварительно объявлять переменные или указывать их тип. Чтобы они появились на свет, достаточно лишь упомянуть их имя, а их типом станет тип присваиваемого им значения. Это сильно отличается от более традиционных языков программирования вроде С, в которых нужно объявлять все переменные и указывать их тип до их использования. Но такая «динамическая типизация» присуща более современным языкам программирования, наподобие РНР и Python.
Запустив эту программу, вы скорее всего увидите, что ответ равен 65534. Данный UID принадлежит несколько лживой учетной записи с именем "nobody". Чтобы игнорировать эту строку, можно добавить шаблон во второе выражение таким образом:
BEGIN { maxuid = 0; FS = ":"}
$1 != "nobody" {if ($3 > maxuid) maxuid = $3 }
END { print "the largest UID is", maxuid }

Вот еще один пример, наугад взятый из Ubuntu, из файла /etc/init.d/vmware:
count=`sbin/lsmod | awk 'BEGIN {n = 0} {if ($1 == "'"$driver"'") n = $3) END {print n}"

Это опять же пример подстановки команды, в котором переменная $driver (определенная ранее в скрипте) довольно замысловато окружена кавычками.

Эмуляция других утилит
Awk — утилита общего назначения, которая умеет эмулировать всевозможные специализированные утилиты. Например, следующая команда добавляет номера строк в файл foo и эквивалентна cat -n:
$ awk'{print NR, $0)' foo

В этом примере подсчитываем количество слов во входном потоке, он эквивалентен wc -w: 
$ awk '{w += NF) END { print w }' foo

А этот пример по сути эквивалентен grep:
$ awk '/^chris/' /etc/passwd 
chris:x:1000:1000:Chris Brown,,,:/home/chris:/bin/bash

В этом примере у программы есть шаблон (регулярное выражение, соответствующее '^chris'), но нет действия. Действие Awk по умолчанию — просто вывести строку, как и делает grep.
Все следующие примеры основаны на «списке покупок»:
$ cat shopping
Супермаркет 1 Курица 4.55 Супермаркет 50 Вешалки 1.25 Булочная 3 Хлеб 2.40 Хозтовары 1 Шланг 15.00 Одежда 1 Брюки 24.99 Хозтовары 2 Дверная ручка 8.40 Супермаркет 2 Молоко 1.25 Одежда 6 Носки 9.00 Хозтовары 2 Отвертка 2.00 Одежда 2 Юбка 28.00 Хозтовары 20 Наждак 10.00 Булочная 10 Плюшка 1.95 Булочная 2 Пирожок 6.50 Хозтовары 50 Гвозди 0.95
Этот файл намеренно структурирован, и именно такие файлы любит разбирать Awk. Взгляните на этот файл, иначе вы не поймете примеры.
Сперва выведем общее количество товаров, которые мы наметили купить. Для этого достаточно суммировать значения во втором столбце:
$ awk '{items += $2} END {print items}' shopping

Затем отобразим суммарный счет на покупку. Для этого нужно умножить количество каждого товара на его цену и сложить результаты:
$ awk '{cost += $2 * $4 } END {print cost}' shopping

Предположим, что нам нужно вычислить только стоимость инструментов ("Хозтовары"). Для этого достаточно добавить регулярное выражение:
$ awk '/^Хозтовары/ {cost += $2 * $4} END { print cost }' shopping

Теперь найдем самый дорогой товар: 
$ awk '{if ($4 > max) max = $4 } END { print max}' shopping

Здесь max — просто переменная, которую я придумал. Это не встроенная функция или что-то в этом роде. Опять же обратите внимание на динамическую типизацию — мне не нужно предварительно объявлять переменную max или инициализировать ее нулем.
Затем мы получаем список всех товаров (из файла "lotsofthem"), которых нам нужно по десять или больше штук.
Этот пример нужен в основном для того, чтобы показать, как перенаправить вывод в файл: 
$ awk '$2 >= 10 { print $3 > "lotsofthem" }' shoppin
g
Проясним, что символ ">" интерпретируется не оболочкой, a Awk. Он означает «записать вывод в указанный файл».
Вот действительно компактный пример, который разбивает данные на несколько файлов по одному для каждой категории: 
$ awk '{print > $1 }'shopping

Здесь $1 (категория покупок) определяет имя выходного файла. После выполнения команды у вас появятся файлы Булочная, Хозтовары и т.д. Мне нравится этот пример! Если вас еще не впечатляет мощь однострочных программ, я сдаюсь.
Следующий пример разбивает расходы по категориям. Здесь я поместил программу Awk во внешний файл под названием catcost:
{cost[$1]+= $2 * $4 }
END { for (cat in cost) print cat, cost[cat] }
...и запустил программу так:
$ awk -f catcost shopping

Булочная 39.7
Одежда 134.99
Хозтовары 283.3
Супермаркет 69.55
В этом примере используется ассоциативный массив (массив, 
индекс += $2 * $4}
3.END{
4.max = 0;
5.for (cat in cost) {
6.if (cost[cat] > max) {
7.max = cost[cat];
8.maxcat = cat; 
9.}
10.}
11.print maxcat;
12.}
Нужно некоторое пояснение. Строка 1 — комментарий. Во второй строке, как в предыдущем примере, стоимость по категориям заносится в ассоциативный массив. Строки 3-12 являются частью действия END. Обратите внимание, что теперь это больше похоже на обычную программу, и мы видим, что выражения завершаются символом ;. В строках 5-10 мы пробегаемся в цикле по категориям, определяя наибольшую стоимость. Если вы уже запыхались, можете облегченно перевести дух — это самая длинная программа на Awk, которую я показал!
Самодостаточные скрипты
Также можно писать самодостаточные скрипты Awk и запускать их прямо из оболочки. Это немного удобнее, чем каждый раз набирать awk -f в командной строке. Вот как это сделать.
Во-первых, добавьте «шапку» в начало скрипта, чтобы Linux знал, какой интерпретатор использовать, например, так:
#!/usr/bin/awk -f
{ cost[$1] += $2 * $4 }
END { for (cat in cost) print cat, cost[cat] }
Затем сделайте скрипт исполняемым, как и любой другой скрипт:
$ chmod u+x catscript 

Теперь можно запустить скрипт напрямую:
$ ./catscript shopping

Если вам нравятся однострочные программы, загляните в www.pement.org/awk/awk1line.txt. Если хотите увидеть более длинные программы на Awk, загрузите руководство по gawk по ссылке www.gnu.org/software/gawk/mannal. Классическая книга по языку — «Язык программирования Awk» Ахо, Вайнбергера и Кернигана.

Автор: DJek Просмотров: 7182


Рейтинг статьи: 0

Общий рейтинг по отношению ко всем статьям автора :
{0 [0]}[max] [ - - - - - - - - - - ]

Общий рейтинг из всех статей на сайте :
{0 [888]} [ - - - - - - - - - - ]

[?]
Комментарии 5

Don_makavelli

Отличная работа, есть вопрос 

0
Статистика комментария: Голосов: 0 Пользователей +/-: 0/0 Гостей +/-: 0/0

Don_makavelli

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

659132 SUR=Aband
182855 SUR=BBusy
96066 SUR=BNoAns

нужно перемножить первое поле на на 4 , второе оставить без изменений , при этом на выходе нужен результат умножения + второе поле , делаю следующие

awk { total[$1] = $1 4 } END { for (cat in total) print total[cat]}  
не пойму как мне описать второе поле чтобы его можно было во втором блоке на выход указать 

0
Статистика комментария: Голосов: 0 Пользователей +/-: 0/0 Гостей +/-: 0/0

awful

Доброго времени суток! Из написанного Вами 
awk { total[$1] = $1 4 } END { for (cat in total) print total[cat]}
не совсем очевидно, что Вы желаете сделать, но я попытался предположить и если проблему я правильно понял, то необходимо сделать так:
$ awk '{x[NR] = $1} END {print x[1]*4+x[2], x[2]}' имя_Вашего_файла
Либо в скрипт добавляете 
{x[NR] = $1} END {print x[1]*4+x[2], x[2]}
Ваш результат
$ ./script test
2819383 182855

10
Статистика комментария: Голосов: 10 Пользователей +/-: 0/0 Гостей +/-: 10/0

alex

awful
Спасибо вам code

0
Статистика комментария: Голосов: 0 Пользователей +/-: 0/0 Гостей +/-: 0/0

qweeqwe

$ awk {x[NR] = $1} END {print x[1] 4+x[2], x[2]} имя_Вашего_файла
Да круто. Только за этот язык денег не дают, по крайней мере здесь в россии

-8
Статистика комментария: Голосов: 2 Пользователей +/-: 0/0 Гостей +/-: 0/2

Добавить комментарий к статье


Ctrl+Enter

Для активации кнопки, введите символы, которые Вы видите на картинки.

новая

тема

Заметки на тему IT

Монитор поиска
[x]
Новое сообщение

Сообщения в чате

Вы спрашиваете у гостей/у зарегистрированных/ У Вас спрашивают
всем Ctrl+Enter
зарегистрированным Ctrl+Enter
Ctrl+Enter

Краткая инструкция по работе с чатом

  • Вы должны ввести имя, которое будет запомнено и применяться для чата и комментариев на сайте.
  • Выбрать одну из возможностей
    "Вы спрашиваете у гостей/
    у зарегистрированных/
    У Вас спрашивают"
  • Кликните на один из способов и появиться дополнительная информация