ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП |
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы. |
6.17. Логические AND, OR и NOT в одном шаблонеПроблемаИмеется готовая программа, которой в качестве аргумента или входных данных передается шаблон. В нее невозможно включить дополнительную логику -например, параметры для управления учетом регистра при поиске, AND и NOT. Следовательно, вы должны написать один шаблон, который будет совпадать с любым из двух разных шаблонов (OR), двумя шаблонами сразу (AND) или менять смысл поиска на противоположный (NOT). Подобная задача часто возникает при получении данных из конфигурационных файлов, Web-форм или аргументов командной строки. Пусть у вас имеется программа, в которой присутствует следующий фрагмент:chomp($pattern = Если вы отвечаете за содержимое CONFIG_FH, вам понадобятся средства для передачи программе поиска логических условий через один-единственный шаблон. Решение Выражение истинно при совпадении /ALPHA/ или /BETA/ (аналогично /ALPHA/ || / BETA/): /ALPHA[BETA/ Выражение истинно, если и /ALPHA/, и /BETA/ совпадают при разрешенных перекрытиях (то есть когда подходит строка "BETALPHA"). Аналогично /ALPHA/ && / BETA/: /"(?=*ALPHA)(?=.*BETA)/s Выражение истинно, если и /ALPHA/, и /BETA/ совпадают при запрещенных перекрытиях (то есть когда "BETALPHA" не подходит): /ALPHA,*BETA|BETA.*ALPHA/s Выражение истинно, если шаблон /PAT/ не совпадает (аналогично $var ! ~ /PAT/): /"('?:(?! PAT), )"$/s Выражение истинно, если шаблон BAD не совпадает, а шаблон GOOD - совпадает: /(?="(?:(?!BAD),)*$)GOOD/s КомментарийПредположим, вы пишете программу и хотите проверить некоторый шаблон на несовпадение. Воспользуйтесь одним из вариантов:if (!($string =~ /pattern/)) {somethingO } # Некрасиво if ( $string !~ /pattern/) {somethingO } # Рекомендуется Если потребовалось убедиться в совпадении обоих шаблонов, примените следующую запись: if ($string =~ /pat1/ && $string =~ /pat2/ ) { something() } Проверка совпадения хотя бы одного из двух шаблонов выполняется так: if ($string =~ /pat1/ | $string =~ /pat2/ ) { something() } Короче говоря, нормальные логические связки Perl позволяют комбинировать логические выражения вместо того, чтобы объединять их в одном шаблоне. Но давайте рассмотрим программу minigrep из примера 6.12, которая в качестве аргумента получает всего один шаблон. Пример 6.12. minigrep #!/usr/bin/perl
Такие шаблоны не всегда работают быстрее. $murray_h ill =~ /bell/ &&
$murray_ hille =~/lab/ сканирует строку не более двух раз, однако для (?=",
*?Ье11 )(?=". *?lab) механизм поиска ищет "lab" для каждого экземпляра
"bell", что в наихудшем случае приводит к квадратичному времени выполнения.
Тем, кто внимательно рассмотрел эти два случае, шаблон NOT покажется тривиальным.
Обобщенная форма выглядит так:
> Смотри также -------------------------------
6.18. Поиск многобайтовых символовПроблемаТребуется выполнить поиск регулярных выражений для строк с многобайтовой кодировкой символов. Кодировка определяет соответствие между символами и их числовыми представлениями. В кодировке ASCII каждый символ соответствует ровно одному байту, однако языки с иероглифической письменностью (китайский, японский и корейский) содержат так много символов, что в их кодировках символы приходится представлять несколькими байтами. Perl исходит из предположения, что один байт соответствует одному символу. В ASCII все работает нормально, но поиск по шаблону в строках, содержащих многобайтовые символы, - задача по меньшей мере нетривиальная. Механизм поиска не понимает, где в последовательности байтов расположены границы символов, и может вернуть "совпадения" от середины одного символа до середины другого.РешениеВоспользуйтесь кодировкой и преобразуйте шаблон в последовательность байтов, образующих многобайтовые символы. Основная мысль заключается в построении шаблона, который совпадает с одним (многобайтовым) символом кодировки, а затем применить этот шаблон "любого символа" в более сложных шаблонах.КомментарийВ качестве примера мы рассмотрим одну из кодировок японского языка, EUC-JP, и разберемся, как воспользоваться ей для решения многих проблем, связанных с многобайтовыми символами. В EUC-JP можно представить тысячи символов, но в сущности эта кодировка является надмножеством ASCII. Байты с 0 по 127 (0х00 - Ox7F) почти точно совпадают с ASCII-аналогами и соответствуют однобайтовым символам. Некоторые символы представляются двумя байтами; первый байт равен Ох8Е, а второй принимает значения из интервала OxAO-OxDF. Другие символы представляются тремя байтами; первый байт равен 0х8 F, а остальные принадлежат интервалу OxAI-OxFE. Наконец, часть символов представляется двумя байтами, каждый из которых принадлежит интервалу OxAI-OxFE. Исходя из этих данных, можно построить регулярное выражение. Для удобства последующего применения мы определим строку $eucjp с регулярным выражением, которое совпадает с одним символом кодировки EUC-JP:my $eucjp = q{ # Компоненты кодировки EUC-JP:
(строка содержит комментарии и пропуски, поэтому при ее использовании
для поиска или замены необходимо указывать модификатор /х). Располагая
этим шаблоном, мы расскажем, как: o Выполнить обычный поиск без "ложных"
совпадений. o Подсчитать, преобразовать (в другую кодировку) и/или отфильтровать
символы. o Убедиться в том, что проверяемый текст содержит символы данной
кодировки. o Узнать, какая кодировка используется в некотором тексте. Во-всех
приведенных примерах используется кодировка EUC-JP, однако они будут работать
и в большинстве других распространенных многобайтовых кодировок, встречающихся
при обработке текстов - например, Unicode, Big-5 и т. д. Страховка от ложных
совпадений Ложное совпадение происходит, когда найденное совпадение приходится
на середину многобайтового представления одного символа. Чтобы избежать
ложных совпадений, необходимо контролировать процесс поиска и следить,
чтобы механизм поиска синхронизировался с границами символов. Для этого
можно связать шаблон с началом строки и вручную пропустить байты, для которых
в текущей позиции не может произойти нормальное совпадение. В примере с
EUC-JP за "пропуск символов" отвечает часть шаблона /(? : $eucjp)*?/. $eucjp
совпадает с любым допустимым символом. Поскольку он применяется с минимальным
квантификатором *?, совпадение возможно лишь в том случае, если не совпадает
то, что идет после него (искомый текст). Рассмотрим реальный пример:
$is_eucjp = m/"(?:$eucjp)*$/xo;
Поиск и обработка многобайтовых символов играет особенно важную роль в Unicode, имеющей несколько разновидностей. В UCS-2 и UCS-4 символы кодируются фиксированным числом байтов. UTF-8 использует от одного до шести бантов на символ. UTF-16, наиболее распространенный вариант Unicode, представляет собой 16-битную кодировку переменной длины. 6.19. Проверка адресов электронной почтыПроблемаТребуется построить шаблон для проверки адресов электронной почты.РешениеЗадача в принципе неразрешима, проверка адреса электронной почты в реальном времени невозможна. Приходится выбирать один из возможных компромиссов.КомментарийМногие шаблоны, предлагаемые для решения этой проблемы, попросту неверны. Допустим, адрес fred&barney@stonehedge. corn правилен и по нему возможна доставка почты (на момент написания книги), однако большинство шаблонов, претендующих на проверку почтовых адресов, бесславно споткнутся на нем. Документы RFC-822 содержат формальную спецификацию синтаксически правильного почтового адреса. Однако полная обработка требует рекурсивного анализа вложенных комментариев - задача, с которой одно регулярное выражение не справится. Если предварительно удалить комментарии:1 while $addr =~ s/\([-()]*\)//g; тогда теоретически можно воспользоваться довольно длинным шаблоном для проверки соответствия стандарту RFC, но и это недостаточно хорошо по трем причинам. Во-первых, не по всем адресам, соответствующим спецификации RFC, возможна доставка. Например, адрес foo@foo. foo. foo, too теоретически правилен, но на практике доставить на него почту невозможно. Некоторые программисты пытаются искать записи MX на серверах DNS или даже проверяют адрес на хосте, обрабатывающем его почту. Такой подход неудачен, поскольку большинство узлов не может напрямую подключиться к любому другому узлу, но даже если бы это было возможно, получающие почту узлы обычно либо игнорируют команду SMTP VRFY, либо откровенно врут. Во-вторых, почта может прекрасно доставляться по адресам, не соответствующим RFC. Например, сообщение по адресу postmaster почти наверняка будет доставлено, но этот адрес не соответствует канонам RFC - в нем нет символа @. В-третьих (самая важная причина), даже если адрес правилен и по нему возможна доставка, это еще не означает, что он вам подойдет. Например, адрес president@whitehouse.gov соответствует стандартам RFC и обеспечивает доставку. И все же крайне маловероятно, чтобы этот адресат стал поставлять информацию для вашего сценария CGI. Отважная (хотя и далеко не безупречная) попытка приведена в сценарии по адресу http://wv)w.perl.com/CPAN/authors/Tom_Christiansen/scripts/ckaddr.gz. Эта программа выкидывает множество фортелей, среди которых - проверка регулярного выражения на соответствие RFC-822, просмотр записей MX DNS и стоп-спис-ки для ругательств и имен знаменитостей. Но и такой подход оказывается откровенно слабым. При проверке почтового адреса мы рекомендуем организовать его повторный ввод, как это часто делается при проверке пароля. При этом обычно исключаются опечатки. Если обе версии совпадут, отправьте на этот адрес личное сообщение следующего содержания: Дорогой someuser@host.com, Просим подтвердить почтовый адрес, сообщенный вами в 09:38:41 6 мая 1999 года. Для этого достаточно ответить на настоящее сообщение. Включите в ответ строку "Rumpelstiltskin", но в обратном порядке (то есть начиная с "Nik..."). После этого ваш подтвержденный адрес будет занесен в нашу базу данных. Если вы получите ответное сообщение и ваши указания будут выполнены, можно с достаточной уверенностью предположить, что адрес правилен. Возможна и другая стратегия, которая обеспечивает лучшую защиту от подделок, - присвойте своему адресату личный идентификатор (желательно случайный) и сохраните его вместе с адресом для последующей обработки. В отправленном сообщении попросите адресата включать личный идентификатор в свои ответы. Однако идентификатор будет присутствовать и при возврате недоставленного сообщения, и при включении рассылки в сценарий. Поэтому попросите адресата слегка изменить идентификатор - например, поменять порядок символов, прибавить или вычесть 1 из каждой цифры и т. д. > Смотри также -------------------------------
6.20. Поиск сокращенийПроблемаПредположим, у вас имеется список команд - например, "send", "abort", "list" и "edit". Пользователь вводит лишь часть имени команды, и вы не хотите заставлять его вводить всю команду до конца.РешениеВоспользуйтесь следующим решением, если все строки начинаются с разных символов или если одни совпадения имеют более высокий приоритет по сравнению с другими (например, если "SEND" отдается предпочтение перед "STOP"):-chomp ($answer = о); if ("SEND" =" /~\Q$answer\i) { print "Action is send\n" } elsit ("STOP" =~ /~\Q$answer\i) { print "Action is stop\n" } elsif ("ABORT" =~ /"\Q$answer\i) { print "Action is abort\n" } elsif ("LIST" =~ /"\Q$answer\i) { print "Action is list\n" } elsif ("EDIT" =~ /"\Q$answer\i) { print "Action is edit\n" } Кроме того, можно воспользоваться модулем Text::Abbrev: use Text::Abbrev; $href = abbrev qw(send abort list edit): for (print "Action: "; <>; print "Action: ") { chomp; my $action = $href->{ lc($_) }; print "Action is $action\n"; } КомментарийВ первом решении изменяется стандартный порядок поиска; обычно слева указывается переменная, а справа - шаблон. Мы бы также могли попытаться опре- делить, какое действие выбрал пользователь, с помощью конструкции $answer= =~ /"ABORT/i. Выражение будет истинным, если $answer начинается со строки "ABORT". Однако совпадение произойдет и в случае, если после "ABORT" в $answer следует что-то еще - скажем, для строки "ABORT LATER". Обработка сокращений обычно выглядит весьма уродливо: $answer =~/"A(B(0(R(T)?)?)?)^$/i. Сравните классическую конструкцию "переменная =~ шаблон" с "ABORT" =" / "\Q$answer/i. \Q подавляет интерпретацию метасимволов, чтобы ваша программа не "рухнула" при вводе пользователем неверного шаблона. Когда пользователь вводит что-нибудь типа "ab", после замены переменной шаблон принимает вид "ABORT" =~ /"аЬ/1. Происходит совпадение. Стандартный модуль Text::Abbrev работает иначе. Вы передаете ему список слов и получаете ссылку на хэш, ключи которого представляют собой все однозначные сокращения, а значения - полные строки. Если ссылка $href создается так, как показано в решении, $href->{$var} возвращает строку "abort". Подобная методика часто используется для вызова функции по имени, вводимому пользователем. При этом применяется символическая ссылка: $name = 'send'; &$name(); Впрочем, это небезопасно - пользователь сможет выполнить любую функцию нашей программы, если он знает ее имя. Кроме того, такое решение противоречит директиве use strict 'refs'. Ниже приведена часть программы, создающая хэш, в котором ключ представляет собой имя команды, а значение - ссылку на функцию, вызываемую этой командой: # Предполагается, что &invoke_editor, &deliver_message, # $file и $PAGER определяются в другом месте. use Text::Abbrev; my($href, %actions, $errors); %actions = ( "edit" => \&invoke_editor, "send" => \&deliver_message, "list" => sub { system($PAGER, Stile) }, "abort" => sub { print "See ya!\n"; exit; } => sub { print "Unknown command: $cmd\n"; $errors++; } }; $href = abbrev(keys %actions); local $_; for (print "Action: "; <>; print "Action: ") { s/-\s+//: s/\s+$//; next unless $_; $actions->{ $href->{ lc($_) } }->(); } Если вы не любите слишком кратких выражений или хотите приобрести навыки машинистки, последнюю команду можно записать так: $abbreviation = 1с($_); $expansion = $href->{$abbreviation}; $coderef = $actions->{$expansion}; &$coderef(); > Смотри также --------------------------------
6.21. Программа: uriifyПрограмма uriify оформляет URL-адреса, найденные в файлах, в виде ссылок HTML. Она работает не для всех возможных URL, но справляется с наиболее распространенными. Программа старается избежать включения знаков препинания, завершающих предложения, в помеченный URL. Программа является типичным фильтром Perl и потому может использоваться для перенаправленного ввода: % gunzip -с '/mail/archive.gz j uriify > archive.uriified Исходный текст программы приведен в примере 6.13. Пример 6.13. uriify#!/usr/bin/perl # uriify - оформление URL-подобных конструкций в виде ссылок HTML $urls = '(http|telnet|gopher|file]wais|ftp)'; $ltrs = o\w'; $gunk = -/#-:.?+=&%@!\-'; $punc = '. :Л-'; $any = "${ltrs}${gunk}${punc}"; while (<>) { s{ \b ( $urls: [$any] +? # Начать с границы слова # Начать сохранение $1 { # Искать имя ресурса и двоеточие, # за которыми следует один или более # любых допустимых символов, но # проявлять умеренность и брать лишь то, # что действительно необходимо .... } # Завершить сохранение $1 } (?= # Опережающая проверка без смещения [$punc]* # либо 0, либо знак препинания, [ "$аnу] # за которыми следует символ, не входящий в url, | # или $ # конец строки ) }{<А HREF= $1">$^}igox; print; }
|