Структура задачи:
- projects
- project1/ — проекты
- conf/
- <run_configurations>*.conf — конфигурации построения отчетов по таблицам
- reports/
- <run_configurations>/
- report1.json — сами отчеты, содержат статистику по таблицам Apache Hive
- report2.json
- <run_configurations>/
- conf/
- project2/
...
- project1/ — проекты
Надо: найти просроченные отчеты.
Итак, расчехляем Bash, открываем отдельный терминал для man-ов и приступаем)
Всех, кому интересно — прошу под кат.
Имеем: внутреннюю систему построения отчетов в виде папки с проектами. В каждом проекте в папке
conf
лежат конфигурации построения отчетов, содержащие в себе имена Hive-овых баз данных в полях "schema"
, по таблицам которых строятся отчеты. В папке reports
— сами отчеты, разложенные в папки с именами конфигураций. Каждый отчет — это json
, содержащий статистику по Hive-овым таблицам в массиве объектов "table"
, а также дату создания в поле "created_date"
. Возьмем ее вместо даты создания файла, раз уж есть. Нам надо найти такие отчеты, в которых содержатся таблицы, которые были изменены после создания отчета.Почему в SQL-стиле? Bash предоставляет большие возможности работы с текстом, разделенным на колонки (обычно пробелами), напоминающие обработку таблиц в SQL.
Инструментарий:
- cat, find, grep и прочее — в представлении не нуждаются)
- sed — используем для тупой автозамены
sed s/что/на что/g
- awk — позволяет отображать/переставлять/сливать колонки, фильтровать строки по содержимому колонок
- sort, uniq — наверное, любимые инструменты разгребателей логов) Первый — сортирует, второй — удаляет/подсчитывает дубликаты. Используются часто для всяких top N
awk '...' log | sort -k field_n | uniq -c | sort -n -r | head -n N
- xargs — обрабатывает поток строк одной командой. Может развернуть строки в argument-list для заданной команды, а может для каждой строки эту команду выполнить.
- join — натуральный SQL-евский INNER JOIN. Сливает 2 сортированных файла по значению одного одинакового поля в один, сначала идет общее поле, затем оставшиеся поля первого файла, потом — второго.
Приступим. Для начала — просто нагрепаем используемые таблицы:
grep -r "\"table\":" projects/*/reports/* | ...
Он отдает данные в таком виде:
projects/project1/reports/run1/report1.json: «table»: «table1»,
projects/project1/reports/run2/report2.json: «table»: «table2»,
projects/project2/reports/run3/report3.json: «table»: «table3»,
...
... | sed 's/:/ /g' | awk '{print $1 " " $3}' | sed 's/[\r\n",:]//g' | ...
... | sort -k 1b,1 | uniq > report_tables
Меняем ':' на пробел, чтобы точно отделить имя файла от колонки «table», печатаем первую (файл отчета) и третью (имя таблицы) колонки, чистим мусор sed-ом, пересортировываем и сохраняем в нашу первую таблицу — report_tables.
Затем таким же способом строим таблицу report_dates, только грепаем created_date и выводим чуть больше колонок (дату и время):
grep -r "\"created_date\":" projects/*/reports/* | sed 's/:/ /g' | ...
... | awk '{print $1 " " $3"T"$4":"$5":"$6}' | sed 's/[\r\n",:]//g' | ...
... | sort -k 1b,1 | uniq > report_dates
Теперь джойним их, сливая имя файла отчета и имя таблицы в одну колонку, и получаем таблицу с файлами отчетов, таблицами и датами создания этого отчета:
join report_tables report_dates | awk '{print $1"#"$2 " " $3}' | ...
... | sort -k 1b,1 > report_table_date
projects/project1/reports/run1/report1.json#table1 2017-08-07T070918.024907
projects/project1/reports/run1/report1.json#table2 2017-08-07T070918.024907
projects/project1/reports/run1/report1.json#table3 2017-08-07T070918.024907
...
Первая часть вроде бы готова. Теперь по аналогии нагрепаем используемые базы:
grep -r "schema\":" projects/*/conf/* | sed 's/:/ /g' | ...
... | awk '{print $3 " " $1}' | sed 's/[\r\n":,]//g' | ...
... | sort -k 1b,1 | uniq > schema_configs
schema1 projects/project1/conf/run1.conf
schema1 projects/project1/conf/run2.conf
schema2 projects/project2/conf/run1.conf
...
Вот и первая трудность. Предыдущая таблица построена по файлам отчетов, а эта — по файлам конфигов. Надо проставить между ними соответствие:
cat schema_configs | awk '{print $2}' | sort | uniq | ...
А теперь задумаемся. Просто поставить
xargs -n1 find ...
мы не можем, так как потеряем саму строку с конфигом, а она нужна. Значит, будем итерироваться циклом. Ну да ладно. Ставим пайп и поехали:... | while read line; do <statements>; done | sort -k 1b,1 > config_reports
Далее пишем все внутри statements:
dir=$(dirname $line); dir2=$(dirname $dir); ...
run=$(echo $line | sed "s/.*\///" | sed 's/\.conf//g'); ...
reps=$(find $dir2/reports/$run/ -name *.json); ...
for r in $reps; do echo $line $r ; done
Выглядит сложно.
dirname
вытаскивает из пути к файлу путь до последнего слеша, этим мы и воспользовались, чтобы подняться выше файла с отчетом на пару уровней ($dir2)
. Следующее выражение run=...
вытаскивает из $line
имя файла run.conf
и обрезает расширение, получая имя конфигурации запуска. Далее reps
— имена файлов с отчетами для данного конфига, и циклом по ним выводим файл с конфигом $line
и файл с отчетом $r
. Пересортировываем и пишем табличку config_reports
.projects/project1/conf/run1.conf projects/project1/reports/run1/report1.json
projects/project1/conf/run1.conf projects/project1/reports/run1/report2.json
projects/project1/conf/run2.conf projects/project1/reports/run2/report3.json
...
Это была самая важная часть работы — проставить соответствие между пространством конфигов и пространством отчетов. Осталось только определить даты последнего изменения таблиц в используемых бд, и у нас будет вся нужная инфа, останется только все правильно переджойнить. Поехали:
cat schema_configs | awk '{print $1}' | sort | uniq | ...
... |sed 's/^/path_in_hive/g' | sed 's/$/\.db/g' | ...
... | xargs -n1 -I dr hdfs dfs -ls dr | sed 's/\// /g' | ...
... | sed 's/\.db//g' | awk '{print $12 " " $13 " " $6"T"$7}' | ...
... | sort -k 1b,1 | uniq > schema_tables
Несмотря на длину, тут все просто. Сначала берем
schema_configs
, оттуда выделяем уникальные схемы, затем sed
-ом приписываем к началу путь к Hive-вому хранилищу, в конец — расширение .db
. Теперь для каждой такой строки выполняем hdfs dfs -ls
, это показывает нам все таблицы в заданной базе с датами их последнего изменения. Меняем все слеши на пробелы, вытаскиваем имя базы, имя таблицы и дату ее изменения, пересортировываем и готова табличка schema_tables
.Теперь заключительная часть:
# configs - tables
join schema_configs schema_tables | awk '{print $2 " " $3 " " $4}' | ...
... | sort -k 1b,1 | uniq > config_tables
# reports - tables hive dates
join config_reports config_tables | awk '{print $2"#"$3 " " $4}' | ...
... | sort -k 1b,1 > report_table_hive_dates
# final!
join report_table_date report_table_hive_dates | sed 's/#/ /g' | ...
... | awk '{if ($3<$4) print $1}' | sort | uniq > outdated_reports
Сначала джойним
schema_configs
и schema_tables
по полю с именем бд, и получаем табличку config_tables
— конфиг, таблица и дата ее последнего изменения. Затем джойним config_reports
и config_tables
, чтобы наконец-то получить соответствие отчет — таблица в Hive. Причем имя файла с отчетом и имя таблицы объединяем в одно поле с помощью #
. Ну и последний штрих — сджойнить report_table_date
и report_table_hive_dates
, разделить имя файла с отчетом и имя таблицы пробелом, и напечатать те отчеты, где дата создания отчета меньше даты изменения таблицы, затем ищем уникальные отчеты, и работа готова.Заключение
Девять довольно простых строк на баше оказалось достаточно, чтобы решить данную задачу. Далее этот скрипт запускаем по крону, и вебморда, ориентируясь на файл
outdated_reports
, может выдать для отчета заголовок "Report is outdated"
(или не выдать).Код тут
Комментарии (36)
onix74
04.12.2017 10:36+2Вместо sed 's/:/ /g' | awk '{print $1 " " $3}' можно использовать awk -F: '{print $1 " " $3}'
Конструкция упростится и станет более читабельнойiboltaev Автор
04.12.2017 10:38Да, можно. Спасибо, как-то сразу не догадался)
guestinger
04.12.2017 15:39Вот эту конструкцию тоже можно заменить для читабельности:
sed 's/[\r\n",:]//g'
на
tr -d '\r",:'
Зачем у вас идёт замена переноса строки (\n)?
Во-первых, сэд её так не сможет заменить — надо считать хотя бы две строки для этого (link).
Во-вторых, если это удастся сделать (тем же tr), то у вас будет всё в одну строку, что нарушит дальнейшую логику.
RPG
05.12.2017 00:39Хотя и сам не прочь крутить подобные портянки, хочу предупредить читателей, этот код — не пример для подражания. Выход за границу 80 символов — сама по себе проблема, и если скрипт "причесать" до читаемого состояния, то в нём будет уже не 9, а без малого сотня строк. И гарантировать, что этот скрипт не развалится от случайно затесавшегося неформатного файлика, в отличие от настоящего парсера, невозможно. А когда он развалится, отладка превратится в ад (если этот факт вообще кто-то заметит).
Также для уменьшения времени работы, количества временных файлов да и просто вероятных ошибок, в Bash есть замечательная конструкция:
while read -r line; do echo $((line**2)) done < <(seq 123)
В отличие от
command | while ...do ... done
она не создаёт новый сабшелл и позволяет избежать ошибок, связанных с потерей переменных.
sub31
05.12.2017 07:49cut -f 1,3 -d ':' вместо sed 's/:/ /g' | awk '{print $1 " " $3}'
не работает?
А sed 's#\(.*\):.*:\(.*\)#\1 \2#g'?
zoroda
Плюсую.
Часто приходится обрабатывать таким образом значительные объёмы данных. Раньше загонял данные в SQL таблицы, строил индексы, писал заросы, экспортировал результат. Лет 7 назад открыл для себя возможность разгребать такие файлы при помощи unix команд. Результаты просто фантастические! Скорость обработки увеличилась даже не в разы — на порядки.
mihmig
Эмм, а скорость «раскуривания» такого скрипта следующим админом (или Вами же, но через полгода-год) как изменилась?
Что мешает установить язык более высокого уровня (с IDE, отладчиком, автокомплитом, подсветкой синтаксиса и проверкой ошибок «на лету»)?
Нет, мы откроем ман в соседней консоли и будем отлаживать скрипт стопятьсотый раз прогоняя скрипт…
Cobolorum
Если «админ» не знает текстовые утилиты unix он просто не компетентен.
Автор просто не напиасл самое важное, что выше приведенный скрипт будет работать десятилетиями и не требовать какого либо сопровождения, в отличии от писанины на «языке высокого уровня»
poxvuibr
А писанина на языке высокого уровня что? в 12 часов превратится в тыкву? :)
tgz
А вы помните все ключи для вызова read? А в мане за сколько найдете что делает ключ -r например?
И я молчу про постоянный эскейпинг кавычек, неявный субшел, башизмы и прочие IFS'ы.
Питон предпочтительнее везде, где bash переваливает на 10 строк.
iboltaev Автор
Ну тогда я спокоен, у меня 9)
zoroda
Да, последнее время занчительно больше делаю на Питоне: заимодействие с API, JSON, HTTP запросы. Но такого рода обработка файлов — это то, с чем башовые утилиты справляются значительно быстрее.
tgz
bash скрипт работает быстрее, чем python? Не верю (с)
poxvuibr
Ну собственно первая строка этого конкретного баш скрипта проходит по всем файлам и ищет там строки с "table". А вторая строка ещё раз проходит по тем же файлам и ищет в них же строки с "created_date". Эти данные записываются в файлы. И потом содержимое файлов джойнится.
Вообще-то надо пройти по файлам один раз и за этот один раз вытащить обе строки и ничего не джойнить, потому что уже и так ясно каким таблицам соответствуют какие даты.
И чем бы ты не описал эти альтернативные действия — питоном ли, джавой или рубями или другим, более ядрёным, башем — всё равно получится быстрее, чем в данном конкретном случае, за счёт более вменяемого алгоритма.
Так что, если речь идёт именно о скорости работы программы, а не о скорости создания скрипта — правильно не верите :)
nekt
Как будто IDE, отладчик, автокомплик, подсветка синтаксиса и проверка ошибок на лету — это панацея и не придется раскуривать ни свою реализацию поиска по файлам, ни свою реализацию sed, ни кастомную грепалку…
poxvuibr
Не панацея, но писать будет легче и быстрее.
Это вы про поиск файлов, содержащия строку? Код будет тривиальным, раскуривать его не придётся.
sed там в коде используется для того, чтобы делать замены в строках. Код ещё более тривиальный, чем поиск по файлам, там вообще даже думать не придётся, чтобы понять что происходит.
grep используется либо для поиска файлов (см первый пункт), либо для отсева строк, что в языке программирования высокого уровня тоже очень просто.
iboltaev Автор
Так и на баше не rocket science. Ну и по опыту, на баше такие штуки пишутся гораздо быстрее, получаются гораздо короче и оказываются гораздо более надежными. Меньше кода — меньше багов (с). Проще поставить пайп, чем написать лишний if или фильтр/замену по регекспу. Если брать support, то да, код write-only, но он таким и задумывался — решить задачу наименьшими силами и забить.
poxvuibr
Для баша нет IDE, которая тебе подскажет и поможет.
Из того, что есть в скриптах, навскидку какие-то трудности представляет только join, потому что его, возможно, в стандартной библиотеке не будет.
Предыдущий оратор тут говорил, что код на языке программирования высокого уровня читать будет не сильно легче, чем вот этот баш, а то и тяжелее.
ymn
для баша есть set -x. Для условных однострочников этого хватает за глаза. Люди, которые хотят рефакторинг и go-to-definition для скриптов на баше, явно делают что-то не так.
poxvuibr
Ну да, используют баш там, где лучше использовать язык программирования высокого уровня.
iboltaev Автор
Не, ну правда. Не нравится баш — не пишите. Не нравится читать код на баше — не читайте. Начальство заставляет — смените начальство. Как-то так.
poxvuibr
Вопрос то вроде в том, что потом легче читать и модифицировать — баш или код на языке высокого уровня, а не в том, нравится мне писать на баше, или нет.
iboltaev Автор
Вкратце ответ — кому как. Я напишу на баше и уволюсь, придете вы и скажете, что это все г**но и надо на питоне, перепишете на питоне, уволитесь, придет условный Ваня и скажет, что питон г**но и надо на баше, и так далее. И это — нормально.
Кто как хочет — тот так и доставляет себе радость и удовольствие.
Тем более задача — написать, чтоб работало сейчас — обычно более приоритетна надо задачей — чтобы было всем легко читать потом.
poxvuibr
Вы тут какое-то колесо сансары описали. Бессмысленный и беспощадный джаггернаут. Я против того, чтобы это было нормой :).
Спорить не буду, нередко решать проблему лучше не тем инструментом, который её лучше решает, а тем, которым ты лучше умеешь пользоваться. Особенно, когда надо сделать так, чтобы заработало прямо сейчас. Если бы в ветке не подняли вопрос поддерживаемости, в общем-то и разговора бы не было.
Хотя, пользуясь случаем, хочу сделать замечание статье. Стиль, используемый в скрипте — не SQL. SQL это декларативный язык, а тут функциональный конвейер.
ihouser
Вот написал человек на Delphi программульку и слился. В свое время этот IDE был на вершине популярности. Прога была хороша, мы были рады до того момента, когда данных стало много и начались тормоза (что то не оптимально написал, не предвидел).
Купили крутую программку от уважаемой компании. Делает все, но чуточку не то что нужно. И сделать так как нам надо они немогут — навороты не позволяют. Вынуждены жрать кактус.
Так что скрипт на bash'е переживет все модные веяния. И понимающий unix команды всегда найдутся.
poxvuibr
Я так понимаю проблема в том, что нет человека, который может поправить исходники и собрать код заново? Ну, то есть, надо было выбирать другй язык программирования?
Программа на java тоже переживёт. Да что там. Мне неприятно это говорить, но даже программа на Питоне будет жить ещё очень долго. Ну и работать не только там, где есть unix tools, конечно :)
ihouser
В то время выбор Delphi был естественным. Сама программа 4 года радовала всех.
А вот Питон — г… о. Постоянно имею проблемы с прогами на питоне. Какой нибудь редкий модуль отвалится а из логов никак непонят что отвалилось. А когда разберешься, окажется что нужна версия модуля которая при сборке вываливается с ошибкой. С прогами на других языках столько гемора не имели.
Конечно, можно винить руки из ж, но в бизнесе чинить программы — муда. Инструменты должны работать незаметно и исправно.
poxvuibr
Я не сомневаюсь.
Вне всякого сомнения это успешный код. Я даже не совсем понимаю, что мешало поправить его и собрать ещё раз. Врядли обошлось бы дороже покупки стороннего решения. Дельфи то сейчас не то чтобы совсем мёртвая штука .
Ну, я бы не был так категоричен. Тем не менее я его не люблю и именно поэтому мне неприятно говорить, что код на питоне — таки штука долговечная.
Сочувствую. У меня немного обратная ситуация — я их писать не люблю. Но питон однозначно лучше баша.
ihouser
Для компании несвязанной с IT найти нужного программиста задача нетривиальная, точнее — лотерея. И в это далекое время мы не знали слова «freelance». Оглядываясь назад, мы многое могли сделать лучше. Был бы тогда теперешний опыт…
Crandel
Если вы не платите за это деньги — то вам никто ничего не должен. А руки да — они из одного места получается
iboltaev Автор
Вообще, это скорее повод следующему админу таки раскурить баш. А то мало ли, вдруг воинствующий джавист придет сопроводжать. Давайте для него версию на java напишем, с GOF-ом и интерфейсами.
poxvuibr
А можно попросить у вас какие-нибудь живые данные? Что там в этих файликах, на которых скрипты бегают?
iboltaev Автор
Данные-то дать могу, но вот Hive-кластер, который в статье упоминается, вам самому поднимать приедтся
poxvuibr
Как я понимаю, от кластера там только xargs -n1 -I dr hdfs dfs -ls dr . Это можно и чем-нибудь другим подменить для целей тестирования.