Тези дни от Държавния архив направиха данните за всички загинали в Балканската война
достъпни на специален Интернет сайт: http://archives.bg/balkanwars/. Наистина не по
най-удобния за използване и обработка начин, но не им се сърдя - те също се учат в движение,
достатъчно е, че са тръгнали в правилната посока. Боян Юруков, който вероятно влага
най-много усилия в каузата "Отворени данни" у нас, е изтеглил всичката информация от сайта и
след допълнителна обработка, я е публикувал на този адрес:
http://opendata.yurukov.net/balkanwars/, като CSV файл с табулация за разделител на
полетата. Целта на този текст е да ви покаже как може да използвате основни Линукс
инструменти за обработка на подобни данни.
И така, имаме CSV файл. Тоест - обикновен текстов файл съдържащ данни, структурирани по
определен начин. Всеки ред от този файл представлява запис за един човек. Всеки запис се
състои от няколко полета, разделени с табулация едно от друго. Първият ред-запис във файла
представлява всъщност списък с етикетите на отделните полета в реда по който са подредени
във всеки ред-запис. Ако сте изтеглили файла с данните от сайта на Боян, можете да го
отворите и да погледнете - петото поле на всеки ред-запис съдържа фамилията на човека, за
когото е записа. Дванадесетото - датата на смъртта му. И така нататък. Така са организирани
данните.
В Линукс, когато стане дума за текстов файл, записи по редове, данни по полета - веднага се
сещаме за awk. С негова и на приятелите му помощ, можем да извлечем информация от
данните. Приятелите на awk са sort и uniq. И така - може би ви интересува в битките при кои
населени места, българската армия е дала най-много жертви? Във файла информацията за мястото
на смъртта се съдържа в поле 13 от всеки запис. С awk можем да извадим само него от всички
данни. Със sort ще сортираме резултата от работата на awk. Инструмента uniq, извикан по
подходящ начин, ще преброи колко пъти се среща всяко име в подредения от sort списък.
Резултата от uniq ще дадем отново на sort, като този път го помолим да го сортира в обратен
ред - от най-голямата стойност към най-малката. И тъй като ни интересуват само най-големите
стойности, ще извикаме head и ще поискаме от него само първите десет стойности от списъка,
изготвен от sort. Следите ли мисълта ми? Ето я цялата операция в един команден ред:
awk 'BEGIN{FS="\t"}{print $13}' zaginali.csv | sort | uniq -c | sort -nr | head -n 10
Красиво :) Резултата от изпълнението изглежда така:
2659
1684 Чаталджа
1502 Одрин
1168 Чорлу
1156 София
1044 Бунар Хисар
817 с. Тарфа
801 Лозенград
739 с. Калфа кьой
718 Хисар
За 2659 от загиналите липсва информация къде са загинали. 1156 са починали в София -
вероятно става дума за ранени и болни, евакуирани и починали в болници.
Малко по-подробен поглед върху awk частта... Това което е оградено с '' представлява
"програмата", която искаме awk да изпълни върху всеки ред от файла, който му даваме. Ето
съставните й части и коя какво прави:
BEGIN{FS="\t"} - указва на awk, че за разделител между полетата в записа използваме
табулация. Налага се, защото по подразбирне awk използва интервал за разделител. BEGIN казва
на awk да изпълни инструкциите в следващия блок {} преди да започне да обработва файла.
{print $13} - инструктираме awk да отпечата 13 поле от всеки ред-запис във файла zaginali.csv.
Това е почти най-простия начин да се използва awk. Толкова прост, че чак е обиден за него и
вероятно тук бихме могли да използваме cut вместо awk.
Със същия команден ред, като го насочим към друго поле от данните, можем да извадим различна
информация. Например за това кой войнишки набор е дал най-много жертви в Балканската война -
набора на загиналите е посочен в седмото поле..
awk 'BEGIN{FS="\t"}{print $7}' zaginali.csv | sort | uniq -c | sort -nr | head -n 10
14337
2985 1906
2326 1908
2300 1907
2297 1905
1886 1904
1807 1909
1640 1911
1615 1910
1512 1912
За 14337 души липсва информация от кой набор са. От останалите, за които има такава -
най-голям брой жертви е дал набор 1906 - 2985 човека. До тук видяхме, че с прости и достъпни
във всяка Линукс дистрибуция инструменти, можем да извличаме информация от данните,
задавайки прости въпроси. А ако искаме да зададем по-сложен въпрос, който да ни даде
по-специфична информация? Например - кой войнишки набор е дал най-много жертви в боевете при
Одрин? На практика комбинираме двата прости въпроса, които използвахме в примерите до сега.
Истината е, че в примерите до сега не сме използвали и 5% от мощта на awk. За него подобни
задачи са тривиални.
awk 'BEGIN{FS="\t"}$13~/Одрин/{print $7}' zaginali.csv | sort | uniq -c | sort -nr | head -n 10
476
77 1906
66 1905
62 1894
59 1912
59 1895
57 1908
56 1904
50 1910
45 1893
Въобще не го затруднихме - поискахме полетата с набора, но само на
записите в които полето за място на смъртта съдържа Одрин. И после sort, uniq и head
свършиха своята част от задачата. Най-много жертви при Одрин е дал набор 1906... А къде е
дал най-много жертви въпросният героичен набор 1906? Да попитаме:
awk 'BEGIN{FS="\t"}$7~/1906/{print $13}' zaginali.csv | sort | uniq -c | sort -nr | head -n 10
118
101 Чаталджа
91 Чорлу
78 с. Тарфа
75 Бунар Хисар
71 Одрин
61 Щип
52 Хисар
48 с. Криволак
46 Лозенград
В боевете при Чаталджа...
Тъй като в последните два примера усложнихме въпроса, инструкциите към awk също леко се усложниха.
BEGIN{FS="\t"} - това вече го знаем. Уведомяваме awk, че разделителя в нашия файл е табулация.
$7~/1906/{print $13} - тук отново искаме от awk да отпечата поле №13, но под условие!
Условието е поле №7 да съдържа 1906 - ако там има друга, различна година, не го искаме. За
това пред инструкцията за отпечатване на поле №13 сме сложили условието $7~/1906/... Сега
awk ще погледне какво има в поле 7 и ако там стои 1906, ще продължи с изпълнението на
инструкциите в блока {}, ако няма 1906 - ще мине направо на следващия ред-запис, без да
отпечата нищо.
Виждате, че Linux има всичко необходимо, за да отговори на въпросите ви, стига вие да можете
да ги формулирате правилно и да му дадете база от данни, от която да извлече отговора. А ако
впрегнете и gnuplot - ще се сдобиете и с красиви графики, които да визуализират
информацията ви. Но за това друг път.