Язык обработки шаблонов 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
В случае использования относительных выражений <
, <=
, ==
, !=
, >=
, >
происходит сравнение чисел, если оба операнда — числа. В противном случае сравниваются строки.