Использование регулярных выражений Выполнила студентка группы 3087 Мишина А.С. май, 2010 г. 1. Анализ log-файла Постановка задачи Для изучения регулярных выражений была написана программа на языке Perl. Она предназначена для анализа лог-файлов, в которых фиксируются обращения посетителей к веб-серверу Apache. Результатом анализа является уровень посещаемости сервера и статистика кодов ошибок обработки запросов к серверу. Данная задача является актуальной, т.к. для осуществления динамичного развития и процветания сервера необходим систематический анализ статистики, полученной путем правильной обработки файлов регистрации событий. Часто возникает необходимость оценить популярность сайта, размещенного на сервере. Например, разработчик сайта может проследить динамику посещений в зависимости от вносимых изменений и модернизации, определить оптимальное направление развития сайта. С другой стороны, количество посещений представляет интерес для пользователя и может индицироваться на соответствующих веб-страницах. Статистика ошибок позволяет выявить уязвимые места сайта и устранить их. Описание алгоритма Алгоритм предусматривает выполнение следующих операций. На первом этапе выполняется чтение файла формата log (apache.log), находящегося в директории, из которой запускается программа. При отсутствии файла данного формата программа выдает соответствующее уведомление: «Error opening apache.log».. На втором этапе производится построчный анализ содержимого файла. Из строки, содержащей информацию об IP-адресе клиента, времени поступления запроса, содержимом запроса, коде ответа сервера клиенту, размере ответа клиенту (в байтах), адресе страницы, откуда пришел посетитель и типе браузера посетителя, выделяется соответственно дата и код ответа и выполняется подсчет количества посещений в день и сбор информации о кодах ошибок и их количестве. Заключительным этапом работы анализатора является визуализация результатов. Результат выполнение программы сохраняется в файле out.txt. Если файл уже существует, то его содержимое заменяется, при отсутствии файла он создается. Выходными данными являются 2 таблицы, содержащие: -результатом анализа является уровень посещаемости сервера; -статистика кодов ответов (ошибок) обработки запросов к серверу и их расшифровки Описание используемых регулярных выражений Структура файла “apache.log”: … 195.54.25.187 - - [05/Mar/2005:06:04:03 +0300] "GET /?what=ref HTTP/1.0" 200 6812 "http://www.yandex.ru/yandsearch?text=%EE%F2%E7%FB%E2+%EE+%F1%F2%F3%E4%E 5%ED%F2%E5&stype=www" "Mozilla/5.0 (Windows; U; Windows NT 5.0; ru-RU; rv:1.7.6) Gecko/20050226 Firefox/1.0.1" … Считывание IP-адреса: my @Ip = ($Text=~m/[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}/g); Дата представлена в следующем виде: 05/Mar/2005:06:04:03 Для того чтобы в массив @dat сохранить все строки – даты, необходимо использовать следующее регулярное выражение: my @date = ($Text =~ m/[0-9]{2}\/[a-z]{3}\/[0-9]{4}\:[0-9]{2}\:[0-9]{2}\:[0-9]{2}/ig); Затем считывается строка до кода ответа включительно, т.е. считываются любое количество вхождений букв, цифр, знаков .,/,[,],+,-,?,/,= и т.д., пока не символ «кавычка», после которого будет идти три цифры подряд – код ошибки. my @all = ($Text =~ m/[a-z0-9 \.\/\[\]\+\-\?\=\&\:_^(" )]*\" [0-9]{3}/ig); Квантификатор (число в фигурных скобках) после символа, символьного класса или группы определяет, сколько раз предшествующее выражение может встречаться. Символ * обозначает любое количество вхождений символов. В конце регулярных выражений использованы модификаторы: i - поиск без учета регистра g - глобальный поиск. Возвращает список всех найденных фрагментов (в контексте массива). Также использование деление считанных строк: @str = split(/\/|:/, $date[$i]); - разделение строки-даты в массив, состоящий из дня, месяца, года и т.д. @tmp = split(/" /, $all[$i]); - разделение строки на нее саму без кода ответа и код ответа; Код программы #открытие файлов apache.log и out.txt, находящихся в директории, из которой запускается программа #при отсутствии данных файлов программа выдает соответствующее уведомление #файл apache.log открывается в режиме чтения, файл out.txt открывается в режиме записи open (MYFILE, "<apache.log") || die "Error opening apache.log"; open (RESULT, ">out.txt"); $Filesize = -s MYFILE; #размер файла read MYFILE, $Text, $Filesize;#чтение файла как одной большой строки #поиск даты my @date = ($Text =~ m/[0-9]{2}\/[a-z]{3}\/[0-9]{4}\:[0-9]{2}\:[0-9]{2}\:[0-9]{2}/ig); #считывание строки до кода ответа включительно my @all = ($Text =~ m/[a-z0-9 \.\/\[\]\+\-\?\=\&\:_^(" )]*\" [0-9]{3}/ig); my @mas; #массив ссылок на массивы, содержащие день, месяц, год и количество посещений my @mistake; #массив ссылок на массивы, содержащие код ответа и его количество my $maxindex=0; #подсчет количества посещений в день for ($i=0; $i<=$#date; $i++){ #разделение строки-даты в массив, состоящий из дня, месяца, года и т.д. @str = split(/\/|:/, $date[$i]); #сохранение нулевой строки-даты if ($i==0){ for ($j=0; $j<3; $j++){ ${$mas[$i]}[$j]=$str[$j]; } ${$mas[$i]}[$j]=1; } else{ #сравнение данной строки с предыдущей, если они совпадают, то значение счетчика увеличивается на единицу if ((${$mas[$#mas]}[2]==$str[2]) && (${$mas[$#mas]}[1]==$str[1]) && (${mas[$#mas]}[0]==$str[0])){ ${$mas[$#mas]}[3]++; } #если строка не совпала, то создается новая ссылка else { $maxindex=$#mas+1; for ($j=0; $j<3; $j++){ ${$mas[$maxindex]}[$j]=$str[$j]; } ${$mas[$maxindex]}[$j]=1; } } } #суммирование по всему массиву mas количества посещений по одной дате for ($j=0; $j<$#mas; $j++){ if (${$mas[$j]}[0]){ for ($k=$j+1; $k<=$#mas; $k++){ if (${$mas[$j]}[0]==${$mas[$k]}[0]){ ${$mas[$j]}[3]=${$mas[$j]}[3]+${$mas[$k]}[3]; ${$mas[$k]}[0]=0; } } } } for ($i=0; $i<=$#all; $i++){ #разделение строки на нее саму без кода ответа и код ответа @tmp = split(/" /, $all[$i]); #сохранение нулевого кода ошибки if ($i==0){ ${$mistake[$i]}[0]=$tmp[1]; ${$mistake[$i]}[1]=1; } else{ $k=0; #сравнение кода ошибки с предыдущими и суммирование в случае совпадения for ($j=0; $j<=$#mistake; $j++){ if ($tmp[1]==${$mistake[$j]}[0]){ ${$mistake[$j]}[1]++; $k=1; break; } } #если код ответа встречается впервые, то создается новая ссылка if (!$k){ ${$mistake[$#mistake+1]}[0]=$tmp[1]; ${$mistake[$#mistake]}[1]=1; } } } select (RESULT); #настройка вывода для выходного файла print "\nУровень посещаемости сайта\n\n"; #операция вывода верней части таблицы $Begining=<<BEGINMARKER; Число │ Месяц │ Год │ Количество посещений │ _______│_______│_______│______________________│ BEGINMARKER print RESULT $Begining; #выбор имени используемого формата $~ = RESULT; #присваивание значений переменным содержащимся в формате и вызов формата функцией write for ($i=0; $i<=$#mas; $i++){ if (${$mas[$i]}[0]){ $NumDate1=${$mas[$i]}[0]; $NumDate2=${$mas[$i]}[1]; $NumDate3=${$mas[$i]}[2]; $NumHit=${$mas[$i]}[3]; write; } } #операция вывода закрытия таблицы $Ending=<<ENDMARKER; _______│_______│_______│______________________│ ENDMARKER print $Ending; #вывод периода, за который проводится статистика, т.е. минимальной и максимальной дат print "\n\nСтатистика ответов(ошибок) за период "; for ($i=0; $i<3; $i++){ print "${$mas[0]}[$i]"; if ($i<2) {print ".";} } print " - "; for ($i=0; $i<3; $i++){ $k = $#mas; while (${$mas[$k]}[0]==0) {$k--;} print ${$mas[$k]}[$i]; if ($i<2) {print ".";} } #создание хеш-таблицы, содержащей коды ответов и их расшифровки %Hash=( 200 => "Запрос обработан успешно", 304 => "Данный код ответа возвращается, если был запрос lf-Modified-Since, и документ не изменялся с указанной даты", … и т.д. осуществляется вывод в файл. Результат работы программы В результате обработки данного файла был получен следующий результат: 2. Нахождение любых чисел Пример использования логических условий для нахождения любых чисел, в том числе и в общепринятой математической записи. Для этого необходимо использовать следующее регулярное выражение: m%(([+-]?(?=\d|[\.,]\d)\d*([\.,]\d*)?((\se|e|\s?\^)([-+]?\d*[,\.]?)\d+)?)|([+-]?e[+]?\d*[,.]?\d+))%gxi; [+-]? - есть ли в перед числом знак + или -. ? - если вообще есть что-то, находящееся внутри впереди стоящего [...]. Regexp (?=\d|[\.,]\d)\d* логический оператор (?=B) требует, чтобы перед числом было B. В данном случае B представляет из себя regex \d|[\.,]\d, это означает, что перед каждым числом должно быть либо просто число, либо число, перед которым стоит либо запятая, либо точка. Далее скобка закрывается и идет \d*. Для остальных цифр и нужен квантификатор \d*, который значит любое количество цифр, в том числе и ноль, т.е. оно работает и для числе вида .2 или ,2 Далее идет регулярное выражение ([\.,]\d*)? которое говорит о том, есть ли вообще точка и запятая и число \d*. Рассмотрим вторую половину регулярного выражения: ((\se|e|\s?\^)([+]?\d*[,\.]?)\d+)?. Эта строчка отвечает за поиск в строке $_ математических обозначений степеней типа e201, E,20 и т.д. В конце стоит знак вопроса, т.е. если степенное обозначение вообще существует. (\se|e|\s?\^) - в "компьютерной" записи вида 2 ^-,3 , т.е. это регулярное выражение позволяет ставить или не ставить пробел при указании степени и использовать знак ^ с пробелом перед ним (или без). Далее идет выражение ([+]?\d*[,\.]?), которое говорит о том, что степень может быть с + или -. Дальше идет цифра \d, затем точка либо запятая. В конце используется \d+, т.е. должно быть хотя бы одно число. В конце использованы модификаторы: m%(что-то)%gxi. i - поиск без учета регистра g - глобальный поиск. Возвращает список всех найденных фрагментов (в контексте массива) x - разрешение разносить регулярное выражение на несколько строк m - после и до символов новой строки Таким образом, регулярным выражением m%(([+-]?(?=\d|[\.,]\d)\d*([\.,]\d*)?((\se|e|\s?\^)([-+]?\d*[,\.]?)\d+)?)|([+-]?e[+]?\d*[,.]?\d+))%gxi; предусмотрены числа степенного порядка, просто числа, числа со знаком, нецелые числа вида ,3 (которое есть 0,3 или 0.3) и числа в "компьютерном" представлении. Пример программы $_=qq~ 1234 34 -4567 3456 -0.35e-0,2 56grf45 -.034 E20 -.034 e2,01 -,045 e-,23 -,034 e201 3e-.20 -,045 e-,23 e-0.88 4 E-0.20 22 E-21 -0.2 w 43 345 2 ^-,3 ~; print "$1\n" while m%(([+-]?(?=\d|[\.,]\d)\d*([\.,]\d*)?((\se|e|\s?\^) ([-+]?\d*[,\.]?)\d+)?)|([+-]?e[+-]?\d*[,.]?\d+))%gxi; В результате работы программы в консольное приложение выводятся все числа.