Perl является прекрасным инструментом для обработки текстовой информации. На каждом компьютере всегда найдется парочка файлов-протоколов (логов) событий тех или иных приложений.
Сисадмины знают, как утомительно выискивать в этих протоколах необходимую информацию или анализировать оную. Хорошо, если у вас на машине только один лог - bootlog.txt, а если десяток, и ежедневно надо шефу давать отчет, кто с какого компьютера куда лазил в Интернете, сколько было посещений вашего сайта, у кого в почтовом ящике 90% развлекательных рассылок с www.sexygirls.com, а 10% - служебной корреспонденции, то вам без инструментария не обойтись. Лучше всего для этих целей использовать perl, - за 10-15 минут можно написать любой анализатор лога на свой вкус.
Анализатор лога
К примеру, есть лог учета трафика машин вашей сети по работе в интернет (вполне реальный лог демона trafd). Обычно он имеет такой вид: from to bytes total date time ws-5.game.net ws-1.game.net 1884 3228 07.10.01 19:04:47 forwarder5.spylog. ws-1.game.net 2164 3164 07.10.01 19:04:47 ws-5.game.net 62.146.24.217 1802 3162 07.10.01 19:04:47 ws-5.game.net entropy.agava.ru 1315 3035 07.10.01 19:04:47 sovet.yandex.ru ws-1.game.net 2729 2933 07.10.01 19:04:47 forwarder5.spylog. ws-5.game.net 2409 2905 07.10.01 19:04:47 ws-1.game.net forwarder5.spylog. 1537 2861 07.10.01 19:04:47 ws-1.game.net search.rambler.ru 1006 2062 07.10.01 19:04:47
Задача: по определенной машине извлечь всю статистику - общее количество байт переданное и принятое извне, и список сайтов, с которыми данный хост (машина) обменивался данными.
Как видим, запись лога состоит из 6 полей определенной длины, поля не разделяются. (Обычно поля разделяются табуляцией или запятыми). Еще определено, что в данном случае в значении поля не может быть пробела. Можно попробовать разделить поля по пробелам. Алгоритм прост = перебирать файл по строке, строку разбивать на поля, и, если совпадает с требуемым хостом, то суммировать данные. Проще простого.
Если вы думаете, что сейчас начнутся циклы с нескончаемым уровнем вложенности, то заблуждаетесь - это оставьте другим языкам программирования. Тут намного проще: tr/ / /s; ($add1,$add2,$stemp,$totsize,$date,$time) = split(/ /,$_);
Вот эти две строчки делают всю работу: сначала из строки ($_) удаляют лишние пробелы, оставляя по одному, и затем режет ее на переменные (split), используя в качестве разделителя пробелы. Если вы с perl не знакомы, то следует пояснить: переменные в perl в основном начинаются со знака доллара $, а переменную по "умолчанию" обозначают как $_.
Хитрая конструкция tr/ / /s; обозначает следующее: найти в переменной "по умолчанию" все пробелы и сжать их tr/ / /s
Чтение файла
Чтобы получить строки из файла, для этого его нужно только открыть и в цикле уже обрабатывать строки: open FILE,"trafd.log"; while () { tr/ / /s; ($add1,$add2,$stemp,$totsize,$date,$time) = split(/ /,$_); .... }
Весьма непривычно и на первый взгляд, лишено смысла. Но, поверьте - работает и весьма эффективно. На тех же сях(язык C) эта обработка заняла бы по крайней мере в четыре раза больше кода. Здесь же конструкция: while () эквивалентна такой (на абстрактном языке): while(!eof(FILE)) $str = readln(FILE); }
Передача параметров в скрипт
Дальше нужно лишь выделить нужные нам записи и просуммировать их траффик. Адрес нужного нам хоста мы будем передавать в командной строке. Кроме того, в качестве параметра можно передавать и имя лог-файла. Как этот параметр получить в perl скрипте: $req_addr = shift; $filename = shift;
Чтобы было ясно: параметры получаем по очереди из командной строки, сдвигая их оператором shift. Это простейший метод, и при его использовании требуется соблюдать очередность параметров. Таким образом переменной $req_addr было присвоено значение первого параметра, а переменной $filename - значение второго параметра.
Хэши
Далее - банальная проверка условия и подсчет траффика: $totalin = 0; $totalout= 0; if ($add1 eq $req_addr) { # подсчитываем входящий траффик$totalin += $totsize; # запомним хост-корреспондент и траффик на него$dh{$add2} += $totsize; } if ($add2 eq $req_addr) { # подсчитываем исходящий траффик$totalout += $totsize; # запомним хост-корреспондент и траффик на него$dh{$add1} += $totsize; }
В условии использован оператор eq, т.е. оператор сравнения == для текстовых выражений. А хитрая конструкция вида $dh{$add1}+=$totsize записывает в именованный массив (хэш) переменную $totsize, причем оператором += суммирует первоначальное содержимое элемента хэша со значением, хранящемся в $totsize. Что такое хэш (hash)? Это массив, у каждого элемента которого есть собственное имя и значение, т.е.: %dh = ('www.yahoo.com', 9845, 'www.yandex.ru', 23632,.....); # или можно записывать так: %dh = ( www.yahoo.com=> 9845, www.yandex.ru=> 23632, ...=>.....);
Нужно помнить, что хэш в целом начинается не с $, а с "процента" - %, но при прямом обращении к элементу нужно использовать запись вида $x{$y} = $value;.
Полный текст скрипта
Вот и все куски кода готовы - нужно из них слепить программку - анализатор лога. Что у нас получится :
#!/usr/bin/perl # первая строка в perl программе - всегда путь на диске к интерпретатору # в данном случае - в Unix - стиле, но можно, # например = c:perlbinperl.exe # Хотя в MS Windows это не обязательно, если это не CGI скрипт. $req_addr = shift; # параметр - адрес хоста $filename = shift; # параметр - имя лог-файла print $filename,"n"; if ( $filename eq 0 ) { # не передали имя лог-файла $filename = "/var/log/trafd.log"; # берем имя по умолчанию } print $filename,"::",$req_addr,"n"; open FILE,$filename; # открываем файл $totalin = 0; $totalout= 0; while () { tr/ / /s; ($add1,$add2,$stemp,$totsize,$date,$time) = split(/ /,$_); if ($add1 =~ $req_addr) { $totalin += $totsize; $dh{$add2} += $totsize; } if ($add2 eq $req_addr) { $totalout += $totsize; $dh{$add1} += $totsize; } } close FILE; # не забыть закрыть файл # Последнее - распечатать результат print "Статистика по хосту ",$req_addr,"n"; print "Исходящий траффик :",$totalin,"n"; print "Входящий траффик :",$totalout,"n"; print "n"; print "В сумме по удаленным хостам:n"; foreach $key (sort keys(%dh)) { foreach (split("", $dh{$key})) { ($out = $_); format STDOUT = @<<<<<<<<<<<<<<<<<<<<<<<< == @>>>>>>>>>>>>>>>> байт $key, $out . write(); } } Всего-то - 45 строк с комментариями! Последнее пояснение - конструкция
Форматированный вывод
format STDOUT =. Это, как вы догадались, строка форматирования вывода в файл STDOUT (т.е. на экран по умолчанию). В шаблоне скобки <<<<<< указывают выравнивание переменной по левому краю, скобки >>>>>> - по правому. Если использовать символы |||||||, то это обозначит выравнивание по центру, ####.## - вывод числа с N знаками после запятой. В поле будет выводится столько символов, сколько есть в строке шаблона. Если вместо знака @ перед шаблонами поставить @*, то данная переменная будет выводится в колонку, если не поместится на одной строке. Если вместо @ использовать ^, то выведется символов столько, сколько есть в строке шаблона, а переменная будет содержать остаток невыведенных символов. Программа готова, можно пользоваться и особых любителей в рабочее время лазить по сайтам сомнительного содержания можно будет "пощемить", чтобы не загружали траффик и не мешали сисадмину рубиться в "Counter-Strike".