Язык обработки шаблонов awk

28.04.2018

Теги: LinuxRegExpШаблон

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
..........
Те, кто привык программировать в bash, возможно ожидали, что команда 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

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

Поиск: Linux • RegExp • awk • Шаблон

Каталог оборудования
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Производители
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
Функциональные группы
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.