Язык обработки шаблонов awk
Awk — скриптовый язык построчного разбора и обработки входного потока (например, текстового файла) по заданным шаблонам (регулярным выражениям). Часто используется в сценариях командной строки. С помощью языка awk можно выполнять следующие действия:
- Объявлять переменные для хранения данных.
- Использовать арифметические и строковые операторы для работы с данными.
- Использовать управляющие операторы и циклы, что позволяет реализовать сложные алгоритмы.
- Создавать форматированные отчёты.
Вызов awk выглядит так:
awk [опции] [программа] [файл]
Awk рассматривает входной поток как набор записей. Каждая запись делится на поля. По умолчанию разделителем записей является символ новой строки (то есть записи — это строки), разделителем полей — символ пробела или табуляции. Символы-разделители можно явно определить в программе.
Опции:
-F fs— позволяет указать символ-разделитель для полей в записи.-f file— указывает имя файла, из которого нужно прочесть awk-скрипт.-v var=value— позволяет объявить переменную и задать её значение по умолчанию.-mf N— задаёт максимальное число полей для обработки в файле данных.-mr N— задаёт максимальный размер записи в файле данных.
Чтение awk-скриптов из командной строки
Скрипты awk, которые можно писать прямо в командной строке, оформляются в виде текстов команд, заключённых в фигурные скобки. Кроме того, текст скрипта нужно заключить в одинарные кавычки:
$ awk '{print "Welcome to awk"}' Первая запись Welcome to awk Вторая запись Welcome to awk
При вызове не указан файл с данными, поэтому awk ожидает поступления данных из STDIN. Чтобы завершить работу awk, нужно передать ему символ конца файла, воспользовавшись сочетанием клавиш CTRL+D.
Позиционные переменные, хранящие данные полей
Одна из основных функций awk заключается в возможности манипулировать данными в текстовых файлах. Делается это путём автоматического назначения переменной каждому элементу в строке. По умолчанию awk назначает следующие переменные каждому полю данных, обнаруженному им в записи:
$0— представляет всю строку текста (запись)$1— первое поле в записи (строке)$2— второе поле в записи (строке)- и так далее
К переменной $n можно обратиться не только с помощью номера 0, 1, 2. Но и использовать переменную или выражение:
var = 1 # выводим значения переменных $1 и $2 print $var, $(var + 1)
Поля выделяются из текста с использованием символа-разделителя. По умолчанию — это пробел или символ табуляции.
$ cat data.txt Это первая строка Это вторая строка Это третья строка $ awk '{print $2}' data.txt первая вторая третья
Если в качестве разделителя полей используется что-то, отличающееся от пробела или табуляции:
$ cat /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin .......... $ awk -F: '{print $1}' /etc/passwd root daemon bin ..........
Использование нескольких команд
Awk позволяет обрабатывать данные с использованием многострочных скриптов. Чтобы передать awk многострочную команду, нужно разделить её части точкой с запятой:
$ echo "My name is Tom" | awk '{$4="Adam"; print $0}' My name is Adam
Первая команда записывает новое значение в переменную $4, а вторая выводит на экран всю строку.
Чтение скрипта awk из файла
Awk позволяет хранить скрипты в файлах и ссылаться на них, используя ключ -f. Подготовим файл user-home.awk, в который запишем следующее:
{ print "Пользователь" $1 "домашняя директория" $6 }
Вызовем awk, указав этот файл в качестве источника команд:
$ awk -F: -f user-home.awk /etc/passwd Пользовательrootдомашняя директория/root Пользовательdaemonдомашняя директория/usr/sbin Пользовательbinдомашняя директория/bin ..........
print вставит пробел между строками. Однако, когда в программе строки оказываются рядом друг с другом, awk сцепляет их без добавления между ними пробела. Пробел между строками означает конкатенацию.
В файле скрипта может содержаться множество команд, при этом каждую из них достаточно записывать с новой строки, ставить после каждой точку с запятой не требуется:
{ user = "Пользователь " home = ", домашняя директория " print user $1 home $6 }
$ awk -F: -f user-home.awk /etc/passwd Пользователь root, домашняя директория /root Пользователь daemon, домашняя директория /usr/sbin Пользователь bin, домашняя директория /bin ..........
Выполнение команд до начала обработки данных
Иногда нужно выполнить какие-то действия до того, как скрипт начнёт обработку записей из входного потока. Для этого можно воспользоваться ключевым словом BEGIN. Команды, которые следуют за BEGIN, будут исполнены до начала обработки данных.
$ awk 'BEGIN {print "Это исходные данные:"} {print $0}' data.txt Это исходные данные: Это первая строка Это вторая строка Это третья строка
Выполнение команд после окончания обработки данных
Ключевое слово END позволяет задавать команды, которые надо выполнить после окончания обработки данных:
$ awk 'BEGIN {print "Это исходные данные:"} {print $0} END {print "Конец исходных данных."}' data.txt Это исходные данные: Это первая строка Это вторая строка Это третья строка Конец исходных данных.
Напишем скрипт следующего содержания и сохраним его в файле begin-end.awk:
BEGIN { print "Пользователи и домашние директории" print "-----------------------------------" print "Пользователь \t Домашняя директория" print "------------ \t -------------------" FS = ":" } { # пробел между аргументами print означает конкатенацию print $1 " \t " $6 } END { print "-----------------------------------" }
Тут, в блоке BEGIN, создаётся заголовок табличного отчёта. В этом же разделе мы указываем символ-разделитель. После окончания обработки файла, благодаря блоку END, создается подвал отчета. Запустим скрипт:
$ awk -f begin-end.awk /etc/passwd Пользователи и домашние директории ----------------------------------- Пользователь Домашняя директория ------------ ------------------- root /root daemon /usr/sbin bin /bin .......... -----------------------------------
Основные встроенные переменные
Кроме позиционных переменных $1, $2, $3, которые позволяют извлекать значения полей, есть еще множество других. Вот некоторые из наиболее часто используемых:
FIELDWIDTHS— разделённый пробелами список чисел, определяющий точную ширину каждого поля данных с учётом разделителей полей.FS— переменная, позволяющая задавать символ-разделитель полей.RS— переменная, позволяющая задавать символ-разделитель записей.OFS— разделитель полей на выводе awk-скрипта.ORS— разделитель записей на выводе awk-скрипта.
По умолчанию переменная OFS настроена на использование пробела. Её можно установить так, как нужно для целей вывода данных:
$ awk 'BEGIN {FS=":"; OFS="-"} {print $1,$6,$7}' /etc/passwd root-/root-/bin/bash daemon-/usr/sbin-/usr/sbin/nologin bin-/bin-/usr/sbin/nologin ..........
В некоторых случаях, вместо использования разделителя полей, данные в пределах записей расположены в колонках постоянной ширины. В подобных случаях необходимо задать переменную FIELDWIDTHS таким образом, чтобы её содержимое соответствовало особенностям представления данных.
При установленной переменной FIELDWIDTHS awk будет игнорировать переменную FS и находить поля данных в соответствии со сведениями об их ширине, заданными в FIELDWIDTHS.
$ cat data.txt 234567890 987654321 736194258 $ awk 'BEGIN {FIELDWIDTHS="3 2 4"} {print $1,$2,$3}' data.txt 234 56 7890 987 65 4321 736 19 4258
Переменные RS и ORS задают порядок обработки записей. По умолчанию RS и ORS установлены на символ перевода строки. Это означает, что awk воспринимает каждую новую строку текста как новую запись и выводит каждую запись с новой строки.
Иногда случается так, что поля в потоке данных распределены по нескольким строкам:
$ cat users.txt Иван Иванов ivanov@mail.ru (926) 765-43-21 Петр Петров petrov@mail.ru (926) 123-45-67 $ awk 'BEGIN {FS="\n"; RS=""; OFS=" : "} {print $1,$3}' users.txt Иван Иванов : (926) 765-43-21 Петр Петров : (926) 123-45-67
Здесь в переменную FS мы записываем символ перевода строки. Это укажет awk на то, что каждая строка в потоке данных является отдельным полем. Кроме того, в переменную RS мы записываем пустую строку. Потому что в файле users.txt блоки данных о разных людях разделены пустой строкой. В результате awk будет считать пустые строки разделителями записей.
Дополнительные встроенные переменные
Помимо встроенных переменных, о которых мы уже говорили, существуют и другие:
ARGC— количество аргументов командной строки.ARGV— массив с аргументами командной строки.ARGIND— индекс текущего обрабатываемого файла в массивеARGV.ENVIRON— ассоциативный массив с переменными окружения и их значениями.ERRNO— код системной ошибки, которая может возникнуть при чтении или закрытии входных файлов.FILENAME— имя входного файла с данными.IGNORECASE— если эта переменная установлена в ненулевое значение, при обработке игнорируется регистр символов.FNR— номер текущей записи в файле данных.NF— общее число полей данных в текущей записи.NR— общее число обработанных записей.
Переменные ARGC и ARGV позволяют работать с аргументами командной строки. При этом скрипт, переданный awk, не попадает в массив аргументов ARGV:
$ awk 'BEGIN {print ARGC,ARGV[0],ARGV[1]}' data.txt 2 awk data.txt
Переменная ENVIRON представляет собой ассоциативный массив с переменными среды:
$ awk 'BEGIN {print ENVIRON["HOME"]}' data.txt /home/evgeniy
Переменные среды можно использовать и без обращения к ENVIRON:
$ awk -v home=$HOME 'BEGIN {print "Моя домашняя директория " home}'
Переменная NF позволяет обращаться к последнему полю данных в записи, не зная его точной позиции:
$ awk 'BEGIN {FS=":"; OFS=":"} {print $1,$NF}' /etc/passwd root:/bin/bash daemon:/usr/sbin/nologin bin:/usr/sbin/nologin ..........
Эта переменная содержит числовой индекс последнего поля данных в записи. Обратиться к данному полю можно, поместив перед NF знак $.
Переменные FNR и NR, хотя и могут показаться похожими, на самом деле различаются. Так, переменная FNR хранит число записей, обработанных в текущем файле. Переменная NR хранит общее число обработанных записей:
$ cat data.txt Это первая строка Это вторая строка Это третья строка $ awk '{print $1,"FNR="FNR,"NR="NR}' data.txt data.txt Это первая строка FNR=1 NR=1 Это вторая строка FNR=2 NR=2 Это третья строка FNR=3 NR=3 Это первая строка FNR=1 NR=4 Это вторая строка FNR=2 NR=5 Это третья строка FNR=3 NR=6
Пользовательские переменные
Как и любые другие языки программирования, awk позволяет программисту объявлять переменные. Имена переменных могут включать в себя буквы, цифры, символы подчёркивания. Однако, они не могут начинаться с цифры:
BEGIN { test = "This is a test" print test }
Каждая переменная или поле могут потенциально быть строкой или числом. Awk рассматривает переменную как строковую, пока не возникает необходимость выполнить операции сложения или конкатенации. Если к числу прибавляется строка, то строка автоматически преобразуется в число. Если к строке «прицепляется» число, то число преобразуется в строку.
Условный оператор if
Однострочный вариант оператора:
$ cat data.txt 10 40 20 30 50 $ awk '{if ($1 > 20) print $1}' data.txt 40 30 50
Если нужно выполнить в блоке if несколько операторов, их нужно заключить в фигурные скобки:
$ awk '{if ($1 > 20) {x = $1 * 2; print x}}' data.txt 80 60 100
условный оператор if может содержать блок else:
{ if ($1 > 20) { x = $1 * 2 } else { x = $1 / 2 } print x }
Цикл while
Цикл while позволяет перебирать наборы данных, проверяя условие, которое остановит цикл.
{ total = 0 current = 1 while (current < 4) { # $current означает обращение к переменным $1, $2, $3 total = total + $current current++ } avg = total / 3 print "Данные: " $0 ", среднее арифметическое " avg }
$ cat data.txt 11 22 33 44 55 66 77 88 99 $ awk -f average.awk data.txt Данные: 11 22 33, среднее арифметическое 22 Данные: 44 55 66, среднее арифметическое 55 Данные: 77 88 99, среднее арифметическое 88
В циклах while можно использовать команды break и continue. Первая позволяет досрочно завершить цикл и приступить к выполнению команд, расположенных после него. Вторая позволяет, не завершая до конца текущую итерацию, перейти к следующей.
Цикл for
Решим задачу расчёта среднего значения числовых полей с использованием цикла for:
{ total = 0 for (i = 1; i < 4; i++) { # $i означает обращение к переменным $1, $2, $3 total = total + $i } avg = total / 3 print "Данные: " $0 ", среднее арифметическое " avg }
Массивы
У awk есть ассоциативные массивы — в качестве индекса можно использовать строку или число. Нет необходимости заранее объявлять размер массива — массив может быть изменен во время выполнения.
name[index] = value
Пример использования массива:
{ fruits["mango"] = "yellow" fruits["orange"] = "orange" print fruits["orange"] "\n" fruits["mango"] }
Удаление элемента массива:
{ fruits["mango"] = "yellow" fruits["orange"] = "orange" delete fruits["orange"] print fruits["orange"] }
Форматированный вывод данных
Команда printf позволяет выводить форматированные данные. Она даёт возможность настраивать внешний вид выводимых данных благодаря использованию шаблонов, в которых могут содержаться текстовые данные и спецификаторы форматирования.
Спецификатор форматирования — это специальный символ, который задаёт тип выводимых данных и то, как именно их нужно выводить. Awk использует спецификаторы форматирования как указатели мест вставки данных из переменных, передаваемых printf. Первый спецификатор соответствует первой переменной, второй спецификатор — второй, и так далее.
%c— воспринимает переданное ему число как код ASCII-символа и выводит этот символ.%d— выводит десятичное целое число.%i— то же самое, что иd.%e— выводит число в экспоненциальной форме.%f— выводит число с плавающей запятой.%g— выводит число либо в экспоненциальной записи, либо в формате с плавающей запятой, в зависимости от того, как получается короче.%o— выводит восьмеричное представление числа.%s— выводит текстовую строку.
{ total = 0 current = 1 while (current < 4) { # $current означает обращение к переменным $1, $2, $3 total = total + $current current++ } avg = total / 3 # обратите внимание на символ новой строки \n printf "Данные: %i %i %i, среднее арифметическое %f\n", $1, $2, $3, avg }
$ cat data.txt 11 22 33 44 55 66 77 88 99 $ awk -f average.awk data.txt Данные: 11 22 33, среднее арифметическое 22.000000 Данные: 44 55 66, среднее арифметическое 55.000000 Данные: 77 88 99, среднее арифметическое 88.000000
Встроенные функции
При работе с awk программисту доступны встроенные функции. В частности, это математические и строковые функции, функции для работы со временем.
Математические функции
cos(x)— косинус x (x выражено в радианах).sin(x)— синус x (x выражено в радианах).exp(x)— экспоненциальная функция.int(x)— возвращает целую часть аргумента.log(x)— натуральный логарифм.rand()— возвращает случайное число с плавающей запятой в диапазоне от 0 до 1.sqrt(x)— квадратный корень из x.
Строковые функции
length([arg])— возвращает длинуarg; еслиargне указан, то выдает длину текущей строки.match(string,pattern)— возвращает позицию вхождения шаблонаpatternв строкуstring; или0, если совпадение не найдено.
RSTART и RLENGTH. RSTART принимает значение начальной позиции, найденной в строке (это значение равно возвращаемому значению). RLENGTH принимает значение длины найденной строки. Если совпадение не найдено, то RSTART равно 0, а RLENGTH равно -1.
index(string,needle)— возвращает начальную позицию вхождения подстрокиneedleв строкуstring; еслиneedleвstringне содержится, возвращает0.split(string,array[,sep])— помещает поля строкиstringв массивarrayи возвращает число заполненных элементов массива; если указанsep, то при анализе строки он понимается как разделитель.sub(pattern,replace[,string])— заменяет в строкеstringпервое вхождение шаблонаpatternна строкуreplace; в случае отсутствия аргументаstring, применяется к текущей записи.gsub(pattern,replace[,string])— аналогичнаsub(), но заменяет все вхождения.substr(string,start,length)— возвращает подстроку строкиstring, начиная с позицииstart, длинойlengthсимволов.tolower()— перевод в нижний регистр.toupper()— перевод в верхний регистр.
Вот как пользоваться этими функциями:
$ awk 'BEGIN {x=exp(5); print x}'
Функция system
Функция system("command") выполняет команду command и возвращает состояние выполненной команды.
$ awk 'BEGIN {d=system("date"); print "Command date return", d}' Сб апр 28 17:16:12 MSK 2018 Command date return 0
Пользовательские функции
При необходимости можно создавать собственные функции awk. Для возвращения значения из функции можно использовать оператор return.
function myprint() { printf "Пользователь %s, домашняя директория %s\n", $1, $6 } BEGIN { FS = ":" } { myprint() }
Шаблоны
В общем случае программа awk имеет вид:
BEGIN { действие } /шаблон/ { действие } /шаблон/ { действие } .......... END { действие }
Каждая запись поочерёдно сравнивается со всеми шаблонами, и каждый раз когда найдено соответствие, выполняется указанное действие. Если шаблон не указан, то действие выполняется для любой записи. Если не указано действие, то запись выводится. Специальные шаблоны BEGIN и END позволяют получить управление перед чтением первой входной строки и после прочтения последней входной строки, соответственно.
$ cat data.txt Петр Иванов;ivanov@mail.ru;(926) 765-43-21 Сергей Петров;petrov@mail.ru;(903) 123-45-67 Иван Сергеев;sergeev@mail.ru;(926) 765-43-00 Андрей Николаев;nikolaev@mail.ru;(903) 345-67-89 Максим Семенов;semenov@mail.ru;(926) 876-54-32
$ awk 'BEGIN {FS=";"} /Иван/ {print $0}' data.txt # строки, совпадающие с шаблоном /Иван/ Петр Иванов;ivanov@mail.ru;(926) 765-43-21 Иван Сергеев;sergeev@mail.ru;(926) 765-43-00
$ awk 'BEGIN {FS=";"} /Иван/ {print $0} /Семен/ {print $0}' data.txt Петр Иванов;ivanov@mail.ru;(926) 765-43-21 Иван Сергеев;sergeev@mail.ru;(926) 765-43-00 Максим Семенов;semenov@mail.ru;(926) 876-54-32
$ awk 'BEGIN {FS=";"} /Иванов/,/Сергеев/ {print $1}' data.txt # все строки между первым и вторым шаблоном Петр Иванов Сергей Петров Иван Сергеев
$ awk 'BEGIN {FS=";"} $3 ~ /903/ {print $1" "$3}' data.txt # номер телефона содержит 903 Сергей Петров (903) 123-45-67 Андрей Николаев (903) 345-67-89
$ awk 'BEGIN {FS=";"} $3 !~ /903/ {print $1" "$3}' data.txt # номер телефона не содержит 903 Петр Иванов (926) 765-43-21 Иван Сергеев (926) 765-43-00 Максим Семенов (926) 876-54-32
В предпоследнем примере $3 ~ /903/ означает, что третье поле содержит строку 903. В последнем примере $3 !~ /903/ все наоборот — третье поле не должно содержать строку 903.
В шаблонах можно использовать регулярные выражения:
$ awk 'BEGIN {FS=";"} $3 ~ /(67|00)$/ {print $1" "$3}' data.txt # номер телефона заканчивается на 67 или 00 Сергей Петров (903) 123-45-67 Иван Сергеев (926) 765-43-00
Шаблоны в awk это не просто строка или регулярное выражение. Они могут быть произвольными комбинациями относительных выражений (больше, меньше, равно, не равно, …) и регулярных выражений с использованием !, ||, && и круглых скобок:
переменная ~ /шаблон/переменная in массив(переменная, переменная, …) in массив(переменная ~ /шаблон/) || (выражение in массив)(переменная >= 100) && (переменная <= 200)
$ awk 'BEGIN {FS=";"} $3 ~ /67$/ || $1 ~ /Максим/ {print $1" "$3}' data.txt Сергей Петров (903) 123-45-67 Максим Семенов (926) 876-54-32
В случае использования относительных выражений <, <=, ==, !=, >=, > происходит сравнение чисел, если оба операнда — числа. В противном случае сравниваются строки.