AWK. Арсенал программиста

Импорт данных, анализ логов, обработка файлов - задачи из жизненного цикла продукта. Автоматизация таких процессов не оправдана: они разового характера - сделал и забыл.

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

Знакомтесь: AWK

AWK - интерпретируемый скриптовый язык. Имеет C-подобный синтаксис. Построчно обрабатывает входной поток данных. Каждая строка разбивается на поля, после чего к ним применяется алгоритм обработки.

Сценарий выполнения:

 шаблон {действие}
 шаблон {действие}
 . . .

Каждая строка поочередно сравнивается с шаблонами. При совпадении выполняется действие. Допускается указание пустого шаблона. Тогда действие выполняется над всеми строками. В языке предопределены шаблоны BEGIN и END. BEGIN выполняется до начала обработки, END - в конце.

Действие - набор операторов. Каждый оператор разделяется точкой с запятой, переводом строки или закрывающей скобкой.

Hello world на языке awk:

{
  print "Hello world!";
  exit;
}

Чем AWK может помочь?

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

AWK съэкономит время на импорте данных в СУБД. Идея заключается в преобразовании исходных данных в команды SQL. Сценарий трансформации можно повторно использовать. Скорость загрузки не зависит от прикладных транзакций, кэша ORM и других накладных ресурсов.

Преобразование данных в SQL

Рассмотрим сценарий импорта на примере конкретной задачи.

Задача

Загрузить номера телефонов пользователей в систему.

Таблица contact:

TABLE contact (
  id            bigint,
  type          varchar(1024),
  contact      varchar(1024),
  person_fk     bigint
)

Файл импорта:

Иванов;Иван;Иванович;10.05.65;+79272101010
Петров;Генадий;Николаевич;18.01.80;+79272101894
Николаева;Жанна;Александровна;24.12.1991;+79273164132

SQL запрос добавления номера телефона Иванова Ивана Ивановича:

INSERT INTO contact(id,type,contract,person_fk)
SELECT nextval('id_seq'), 'PHONE', '+79272101010', person.id FROM person
WHERE 
    last_name = 'Иванов' 
    and first_name = 'Иван' 
    and middle_name = 'Иванович' 
    and birth_date = '1965-05-10' 
    and NOT EXISTS(SELECT 1 FROM contact WHERE 
        person_fk = person.id 
        and contact.type = 'PHONE' 
        and contact.contact = '+79272101010'
        );

NOT EXISTS исключает уже зарегистрированные номера

Формат даты рождения может принимать вид ДД.ММ.ГГ ДД.ММ.ГГГГ. Для преобразование в ISO формат потребуются функции strftime, mktime.

AWK скрипт обработки:

{
 current_year_part = strftime("%y", systime());
 split($4,birth_date_parts,".");
 if (length(birth_date_parts[3]) < 4)
   birth_date_parts[3] = birth_date_parts[3] > current_year_part ? birth_date_parts[3] + 1900: birth_date_parts[3] + 2000
}
{
 last_name = $1
 first_name = $2
 middle_name = $3

 birth_date = strftime("%Y-%m-%d",mktime(birth_date_parts[3] " " birth_date_parts[2] " " birth_date_parts[1] " " 0 " " 0 " " 0))

 phone_number = $5
}
{
 printf "INSERT INTO contact(id,type,contract,person_fk) \n \
    SELECT nextval('id_seq'), 'PHONE', '%s', person.id FROM person \n \
    WHERE \n \
            last_name = '%s' \n \
        and first_name = '%s' \n \
            and middle_name = '%s' \n \
            and birth_date = '%s' \n \
            and NOT EXISTS(SELECT 1 FROM contact WHERE \n \
                person_fk = person.id \n \
                and contact.type = 'PHONE' \n \
                and contact.contact = '%s' \n \
            ); \n \
",phone_number, last_name, first_name, middle_name, birth_date, phone_number
}

В заключение

AWK - быстрый инструмент способный приготовить "сырые" данные. Adam Drake сравнил Hadoop с AWK. Файл размером 1.75 Гб был обработан за 12 секунд. Производительность составила 270 MB/сек против 1.14 MB/сек (26 минут) со стороны Hadoop.