Showing posts with label perlfaq. Show all posts
Showing posts with label perlfaq. Show all posts

Saturday, January 21, 2012

perlfaq9 — работа в сети http://beshenov.ru/perlfaq/9.html

http://beshenov.ru/perlfaq/9.html

perlfaq9 — работа в сети

Этот раздел FAQ отвечает на вопросы по работе в сети Internet и отчасти — в WWW.
Какой должен быть ответ CGI-скрипта?
CGI-скрипт работает в командной строке, но не в браузере (ошибка №500)
Как получить более хорошие сообщения об ошибках в CGI-программе?
Как удалить HTML-разметку из строки?
Как извлечь URL?
Как скачать файл с пользовательского компьютера? Как открыть файл на другом компьютере?
Как сделать раскрывающийся список HTML?
Как скачать HTML-файл?
Как автоматизировать отправку HTML-форм?
Как декодировать или создать эти % в URL?
Как перенаправить на другую страницу?
Как защитить веб-страницы паролем?
Как отредактировать в Perl файлы .htpasswd и .htgroup?
Как убедиться в том, что пользователи не введут в форму значения, которые заставят CGI-скрипт сделать что-то нехорошее?
Как разобрать заголовки почтового сообщения?
Как декодировать CGI-форму?
Как проверить на корректность адрес электронной почты?
Как раскодировать строчку из MIME/BASE64?
Как определить почтовый адрес пользователя?
Как отправить почту?
Как использовать MIME, чтобы добавить в сообщение вложение?
Как прочитать почту?
Как узнать имя узла, домена, IP-адрес?
Как скачать сообщение из группы новостей или список активных групп?
Как скачать/закачать файл по FTP?
Как в Perl делать удаленный вызов процедур (RPC)?

Какой должен быть ответ CGI-скрипта?

(Отвечает Алан Флейвелл)
CGI определяет интерфейс между программой (CGI-скриптом) и веб-сервером (HTTPD). Это относится не только к Perl, и по CGI есть отдельные FAQ, руководства, а также группа новостейcomp.infosystems.www.authoring.cgi.
Спецификация CGI изложена в информационном RFC 3875.
Другая документация по теме перечислена в «CGI Programming MetaFAQ».
Эти FAQ по Perl очень избирательно покрывают некоторые вопросы CGI. Однако программирующим на Perl настоятельно рекомендуется использовать модуль CGI.pm, чтобы позаботиться о деталях.
Заголовки ответа CGI (определенные в спецификации CGI) намеренно схожи с заголовками ответа HTTP (определенными в спецификации HTTP, RFC 2616), но это может сбить с толку.
Спецификация CGI определяет два типа скриптов: с анализируемыми заголовками («Parsed Header»), и с неанализируемыми заголовками («Non Parsed Header», NPH). Посмотрите в документации своего сервера, что он поддерживает. Скрипты с анализируемыми заголовками во многих отношениях проще. Спецификация CGI допускает любое обычное представление перевода строки в ответе CGI (создание правильного ответа HTTP на основе CGI — уже дело сервера). Поэтому запись "\n" в текстовом режиме технически корректна и рекомендована. Скрипты с неанализируемыми заголовками более сложны: они должны отдавать полный и правильный набор HTTP-заголовков; спецификация требует завершать записи с CR и LF, то есть, ASCII-символами \015\012, записанными в бинарном режиме.
CGI.pm дает превосходную платформонезависимость, в том числе поддержку систем с EBCDIC. CGI.pmвыбирает правильное представление перевода строки ($CGI::CRLF) и соответствующим образом устанавливает binmode.

CGI-скрипт работает в командной строке, но не в браузере (ошибка №500)

Проблема может иметь различные источники. Посмотрите руководств «Troubleshooting Perl CGI scripts». Если вы его прочитали, но для проблемы не нашлось простого решения, то любезный совет можно получить вcomp.infosystems.www.authoring.cgi (если дело в протоколе HTTP или CGI). Сообщения, которые выдаются за вопросы по Perl, но на деле касаются CGI, обычно не очень хорошо принимаются в comp.lang.perl.misc.
Полезные FAQ, документы по теме и руководства по устранению проблем перечислены в «CGI Meta FAQ».

Как получить более хорошие сообщения об ошибках в CGI-программе?

Используйте модуль CGI::Carp. Он заменяет warn и die, а также функции carpcroak и confess обычного модуля Carp более информативными и безопасными. Модуль также заносит сообщения в обычный журнал ошибок сервера.
use CGI::Carp; warn "Это жалоба"; die "А это уже серьезная ошибка";
Следующее обращение к CGI::Carp также перенаправляет ошибки в заданный файл, и помещено в блок BEGIN для перехвата предупреждений этапа компиляции:
BEGIN {     use CGI::Carp qw(carpout);     open(LOG, ">>/var/local/cgi-logs/mycgi-log")         or die "Не могу дописать в mycgi-log: $!\n";     carpout(*LOG); }
Можете даже организовать вывод критических ошибок в браузере клиента, что удобно для отладки, но может сбить с толку конечного пользователя.
use CGI::Carp qw(fatalsToBrowser); die "Плохая ошибка";
Даже если ошибка случается до вывода HTTP-заголовка, модуль пытается перехватить ее, чтобы избежать вывода пугающих ошибок сервера №500. Простые предупреждения также будут заноситься в обычный журнал ошибок сервера (или куда-либо еще в соответствии с carpout) с указанием имени приложения и времени.

Как удалить HTML-разметку из строки?

Самый правильный, хотя и не самый быстрый способ — использовать HTML::Parser из CPAN. Другой почти правильный подход — HTML::FormatText, который не только удаляет HTML-разметку, но и пытается немного отформатировать полученный простой текст.
Многие пытаются обойтись простыми регулярными выражениями вроде s/<.*?>//g, но это часто не работает, потому что теги могут разрываться переводами строки и содержать экранированные угловые скобки; также могут встречаться HTML-комментарии. Кроме того, при этом упускается преобразование сущностей вроде &lt;.
Вот один простой подход, работающий для большинства файлов:
#!/usr/bin/perl -p0777 s/<(?:[^>'"]*|(['"]).*?\1)*>//gs
Если нужно более полное решение, смотрите трехэтапную обработку в программе striphtml.
Вот некоторые хитрые ситуации, о которых нужно помнить при выборе решения:
<IMG SRC = "foo.gif" ALT = "A > B">  <IMG SRC = "foo.gif"  ALT = "A > B">  <!-- <Комментарий> -->  <script>if (a<b && a>c)</script>  <# Просто данные #>  <![INCLUDE CDATA [ >>>>>>>>>>>> ]]>
В HTML-комментариях с другими тегами эти решения также споткнутся на таком тексте:
<!-- Эта часть закомментирована.     <B>Меня не видно!</B> -->

Как извлечь URL?

Все возможные URL из HTML можно извлечь при помощи HTML::SimpleLinkExtor — поддерживаются aimg,objectframe и многие другие теги, которые могут содержать URL. Если нужно что-то более хитрое, то можете наследовать свой класс от HTML::LinkExtor или HTML::Parser. Можете даже использоватьHTML::SimpleLinkExtor в качестве примера для кода, заточенного под ваши нужды.
Для извлечения URL из произвольного текстового документа можно использовать URI::Find.
Упрощенные решения на регулярных выражениях могут сэкономить много времени на обработке, если известно, что ввод простой. Это решение Тома Кристиансена работает в сто раз быстрее программ, использующих модули, но извлекает только ссылки из элементов A с первым и единственным атрибутом HREF:
#!/usr/bin/perl -n00 # qxurl - tchrist@perl.com print "$2\n" while m{     < \s*       A \s+ HREF \s* = \s* (["']) (.*?) \1     \s* > }gsix;

Как скачать файл с пользовательского компьютера? Как открыть файл на другом компьютере?

Здесь «скачивание с компьютера» сводится к закачиванию, реализованному в HTML-формах. Вы позволяете пользователю указать файл для отправки на сервер. Для вас это выглядит как скачивание, а для пользователя это закачивание. Как ни называйте, это делается при помощи кодирования вmultipart/form-data. Модуль CGI.pm (распространяется с Perl как часть стандартной бибиотеки) поддерживает это в методе start_multipart_form() (не путать с startform()).
За примерами и подробностями обратитесь к разделу документации CGI.pm по закачке файлов.
(Отвечает Брайан Д. Фой)
В модуле CGI.pm (прилагается к Perl) есть функции для создания компонентов форм HTML.
use CGI qw/:standard/; print header,     start_html('Любимые животные'),      start_form,         'Какое ваше любимое животное? ',     popup_menu(         -name   => 'animal',         -values => [ qw( Лама Альпака Верблюд Баран ) ]         ),     submit,         end_form,     end_html;
Дополнительные примеры см. в документации CGI.pm.

Как скачать HTML-файл?

Один из подходов, если в системе установлен консольный браузер lynx:
$html_code = `lynx -source $url`; $text_data = `lynx -dump $url`;
Модули CPAN libwww-perl (LWP) дают более мощное решение. Они не требуют lynx, но тоже могут работать через прокси:
# Простейшая версия use LWP::Simple; $content = get($URL);  # либо вывести документ по указанному URL use LWP::Simple; getprint "http://www.linpro.no/lwp/";  # либо вывести ASCII-версию документа по указанному URL # (требуется пакет HTML-Tree из CPAN) use LWP::Simple; use HTML::Parser; use HTML::FormatText; my ($html, $ascii); $html = get("http://www.perl.com/"); defined $html     or die "Не могу загрузить документ с http://www.perl.com/"; $ascii = HTML::FormatText->new->format(parse_html($html)); print $ascii;

Как автоматизировать отправку HTML-форм?

Если вы делаете что-то сложное — например, работаете с многими страницами и формами, то можете использовать WWW::Mechanize. Подробности смотрите в документации.
Если вы отправляете значения при помощи метода GET, то создайте URL и кодируйте данные формы при помощи метода query_form:
use LWP::Simple; use URI::URL;  my $url = url('http://www.perl.com/cgi-bin/cpan_mod'); $url->query_form(module => 'DB_File', readme => 1); $content = get($url);
Если вы используете метод POST, то создайте собственный пользовательский агент ии кодируйте контент соответствующим образом.
use HTTP::Request::Common qw(POST); use LWP::UserAgent;  $ua = LWP::UserAgent->new(); my $req = POST 'http://www.perl.com/cgi-bin/cpan_mod',                [ module => 'DB_File', readme => 1 ]; $content = $ua->request($req)->as_string;

Как декодировать или создать эти % в URL?

(Отвечает Брайан Д. Фой)
Эти % представляют специальные символы в URI, как описано во втором разделе RFC 2396. Такая кодировка заменяет специальные символы шестнадцатеричными значениями из таблицы. Например, вместо «:» используется «%3A».
Если в CGI-скриптах используется CGI.pm, то о декодировании URI не нужно заботиться. Обрабатывать URL вручную не требуется, ни на входе, ни на выходе.
Если нужно самостоятельно кодировать строку, то никогда не кодируйте сформированный URI. Нужно обработать компоненты отдельно, а затем объединить. Для кодирования строки можно использовать модульURI::Escape; функция uri_escape возвращает экранированную строку:
my $original = "Colon : Hash # Percent %";  my $escaped = uri_escape( $original )  print "$string\n"; # 'Colon%20%3A%20Hash%20%23%20Percent%20%25%20'
Для декодирования строки используйте функцию uri_unescape:
my $unescaped = uri_unescape( $escaped );  print $unescaped; # вернуться к исходной строке
Если хотите сделать это сами, то просто замените специальные символы их кодами. Одно из решений — глобальная подстановка
# кодировать $string =~ s/([^^A-Za-z0-9\-_.!~*'()])/ sprintf "%%%0x", ord $1 /eg;  # декодировать $string =~ s/%([A-Fa-f\d]{2})/chr hex $1/eg;

Как перенаправить на другую страницу?

Укажите полный целевой URL (даже если это на том же сервере). Это один из двух возможных ответов CGI с заголовком Location, определенный спецификацией для скриптов с анализируемыми заголовками. Другой возможный ответ — абсолютный URLpath — разрешается на стороне сервера без перенаправления по HTTP. Ни в первом, ни во втором случае, спецификации CGI не позволяют использовать относительные URL.
Настоятельно рекомендуется использовать CGI.pm. В следующем примере используется полный URL. Перенаправление производится веб-браузером.
use CGI qw/:standard/;  my $url = 'http://www.cpan.org/'; print redirect($url);
В примере ниже используется абсолютный URLpath. Перенаправление производится локальным веб-сервером.
my $url = '/CPAN/index.html'; print redirect($url);
Напрямую это можно записать следующим образом (последний "\n" для ясности указан отдельно):
print "Location: $url\n";   # заголовок ответа CGI print "\n";                 # конец заголовков
$url может быть полным URL или абсолютным URLpath.

Как защитить веб-страницы паролем?

Для включения аутентификации нужно настроить веб-свервер. Настройки могут быть разными, в этом Apache отличается от iPlanet, а iPlanet отличается от IIS. Подробности смотрите в документации к вашему серверу.

Как отредактировать в Perl файлы .htpasswd и .htgroup?

Подходящиий объектно-ориентированный интерфейс для работы с этими файлами, не зависимо от их расположения, предоставляют модули HTTPD::UserAdmin и HTTPD::GroupAdmin. Базы данных могут быть в текстовом формате, DBM, Berkeley DB и любом другом виде, совместимом с DBI. HTTPD::UserAdminподдерживает файлы, используемые в схемах авторизации «Basic» и «Digest». Вот пример:
use HTTPD::UserAdmin (); HTTPD::UserAdmin     ->new(DB => "/foo/.htpasswd")     ->add($username => $password);

Как убедиться в том, что пользователи не введут в форму значения, которые заставят CGI-скрипт сделать что-то нехорошее?

См. ссылки по вопросам безопасности в «CGI Meta FAQ».

Как разобрать заголовки почтового сообщения?

Быстрое и неаккуратное решение есть в документации для split в perlfunc:
$/ = ''; $header = <MSG>; $header =~ s/\n\s+/ /g;     # объединить продолжающиеся строки %head = ( UNIX_FROM_LINE, split /^([-\w]+):\s*/m, $header );
Это решение не сработает верно, если, например, вы попытаетесь обработать все строки с Received. Более полный подход используется в модуле CPAN Mail::Header (часть пакета MailTools).

Как декодировать CGI-форму?

(Отвечает Брайан Д. Фой)
Используйте модуль CGI.pm, прилагающийся к Perl. Он быстрый, простой и берет на себя часть работы, чтобы удостовериться, что всё работает верно. Модуль поддерживает запросы GETPOST и HEAD, данныеmultipart/form, поля со множественными значениями, комбинации запросов и HTTP-сообщений и прочие вещи, о которых вам не хотелось бы заботиться.
Проще некуда: модуль CGI автоматически разбирает ввод и делает каждое значение доступным через функцию param().
use CGI qw(:standard);  my $total = param( 'price' ) + param( 'shipping' );  my @items = param( 'item' ); # несколько значений для одного поля
CGI.pm поддерживает и объектно-ориентированный подход.
use CGI;  my $cgi = CGI->new();  my $total = $cgi->param( 'price' ) + $cgi->param( 'shipping' );  my @items = $cgi->param( 'item' );
Кроме того, можете посмотреть облегченную версию CGI::Minimal. Другие модули CGI::* из CPAN также могут подойти лучше.
Многие пытаются написать собственный декодер (или скопировать из другой программы) и натыкаются на одну из многих тонкостей. Гораздо проще использовать CGI.pm.

Как проверить на корректность адрес электронной почты?

(Часть ответа от Аарона Шермана)
Это не так просто, как кажется. Есть два момента:
  1. Как проверить, что адрес правильно введен?
  2. Как проверить, что адрес указывает на существующего получателя?
Без отправки письма по адресу и проверки, что на другом конце вам ответят, на второй вопрос нельзя дать точный ответ, но модули Email::Valid и RFC::RFC822::Address позволяют ответить на первый и частично на второй, насколько это возможно в реальном времени.
Если хотите только проверить корректность адреса простым регулярным выражением, то можете столкнуться с пробемами, потому что существуют работающие адреса, не соответствующие RFC 2822(последнему стандарту на заголовки электронной почты), ровно как и соответствующие RFC адреса, по которым нельзя доставить почту. Следующий шаблон проверит адреса без комментариев, сворачивающихся пробельных символов и прочих устаревших или необязательных элементов. Этот шаблон соответствует только самому адресу:
my $atom       = qr{[a-zA-Z0-9_!#\$\%&'*+/=?\^`{}~|\-]+}; my $dot_atom   = qr{$atom(?:\.$atom)*}; my $quoted     = qr{"(?:\\[^\r\n]|[^\\"])*"}; my $local      = qr{(?:$dot_atom|$quoted)}; my $domain_lit = qr{\[(?:\\\S|[\x21-\x5a\x5e-\x7e])*\]}; my $domain     = qr{(?:$dot_atom|$domain_lit)}; my $addr_spec  = qr{$local\@$domain};
Просто проверьте адрес на сответствие /^${addr_spec}$/, чтобы посмотреть, следует ли он RFC 2822. Впрочем, нельзя знать, что такой корректный адрес действительно используется определенным человеком, поэтому тут нужно быть аккуратнее.
Наш совет для проверки адресов почты — попросите пользователей вводить их дважды, как при смене пароля. Это исключает опечатки. Если оба ввода совпадают, отправьте на этот адрес личное сообщение. Если на сообщение будет получен ответ, и пользователь выполнит инструкции в нем, то можно быть увереным, что адрес настоящий.
Схожее решенее, менее подверженное подлогам — закрепить за пользователями личные идентификационные номера (PIN), лучше случайные, и хранить вместе с адресами для последующей обработки. В отправляемых сообщениях пользователей нужно просить включить в ответ PIN. Если сообщение возвращается, либо скрипт отвечает, что получатель в отпуске, то PIN всё равно будет в ответе. Поэтому лучше просить пользователей отвечать с несколько измененным PIN, например, записанным в обратном порядке, с добавлением или вычитанием единицы из каждой цифры, и так далее.

Как раскодировать строчку из MIME/BASE64?

Пакет MIME-Base64 из CPAN работает с MIME/BASE64 и MIME/QP. Декодирование BASE64 производится проще некуда:
use MIME::Base64; $decoded = decode_base64($encoded);
Пакет MIME-Tools из CPAN поддерживает выборку и декодирование вложений и самого текста сообщения.
Если строка для декодирования небольшая (меньше 84 байт), то после незначительного преобразования можно использовать прямое решение с форматом u функции unpack():
tr#A-Za-z0-9+/##cd;                   # удалить символы не из base64 tr#A-Za-z0-9+/# -_#;                  # кодировать в UUE $len = pack("c", 32 + 0.75*length);   # вычислить байт длины print unpack("u", $len . $_);         # декодировать UUE и вывести

Как определить почтовый адрес пользователя?

В системах, которые поддерживают getpwuid, переменную $< и модуль Sys::Hostname (из стандартной поставки Perl), можно попробовать нечто вроде
use Sys::Hostname; $address = sprintf('%s@%s', scalar getpwuid($<), hostname);
Правила компании относительно почтовых адресов могут быть такими, что код выше будет давать адреса, не принимаемые почтовой системой, поэтому адреса лучше спросить у самих пользователей. Кроме того, не все системы, на которых работает Perl, выдают подобную информацию, как это делает Unix.
Модуль Mail::Util из CPAN (часть пакета MailTools) предоставляет функцию mailaddress(), которая пытается определить почтовый адрес пользователя. Используется более умный подход, чем в коде выше, с привлечением информации, полученной при установке модуля, но и это может не дать правильных результатов. Опять же, лучше спросить у пользователя.

Как отправить почту?

Используйте напрямую программу sendmail:
open(SENDMAIL, "|/usr/lib/sendmail -oi -t -odq")                     or die "Не могу сделать fork для sendmail: $!\n"; print SENDMAIL <<"EOF"; From: Адрес автора письма <me\@host> To: Конечное назначение <you\@otherhost> Subject: Соответствующий заголовок  Тело письма идет после пустой строки и продолжается нужное число строк. EOF close(SENDMAIL)     or warn "sendmail завершился неудачно";
Опция -oi указывает sendmail не интерпретировать строку, содержащую одну точку, как конец сообщения. С опцией -t получатели определяются по заголовкам; -odq помещает сообщение в очередь. С последей опцией сообщение будет передано не сразу; уберите ее, если хотите отправить его незамедлительно.
Другие, менее удобные решения — прямой вызов mail (иногда называется mailx) или просто сокровенный разговор с удаленным SMTP-демоном на 25-м порту (возможно, с sendmail).
Или можно использовать модуль Mail::Mailer из CPAN:
use Mail::Mailer;  $mailer = Mail::Mailer->new(); $mailer->open({ From    => $from_address,                 To      => $to_address,                 Subject => $subject,               })     or die "Не могу открыть: $!\n"; print $mailer $body; $mailer->close();
Модуль Mail::Internet использует Net::SMTP — менее Unix-ориентированный, чем Mail::Mailer, но и менее надежный. Избегайте чистых SMTP-команд. Есть множество причин использовать агент передачи почты вроде sendmail, в том числе очереди сообщений, записи MX, безопасность.

Как использовать MIME, чтобы добавить в сообщение вложение?

Ответ на вопрос взят из документации к MIME::Lite. Создайте сообщение типа «multipart» (то есть, с вложениями).
use MIME::Lite;  ### Создать новое сообщение типа «multipart»: $msg = MIME::Lite->new(              From    =>'me@myhost.com',              To      =>'you@yourhost.com',              Cc      =>'some@other.com, some@more.com',              Subject =>'Сообщение в двух частях...',              Type    =>'multipart/mixed'              );  ### Добавить части (каждый "attach" вызывается с теми же аргументами, что и "new"): $msg->attach(Type     =>'TEXT',              Data     =>"Вот GIF, который вы просили"              ); $msg->attach(Type     =>'image/gif',              Path     =>'aaa000123.gif',              Filename =>'logo.gif'              );  $text = $msg->as_string;
MIME::Lite также включает метод для отправки
$msg->send;
По умолчанию используется sendmail, но можно настроить использование SMTP через Net::SMTP.

Как прочитать почту?

Хотя можно использовать модуль Mail::Folder из CPAN (часть пакета MailFolder) или Mail::Internet (часть пакета MailTools), часто это перебор. Вот программа для сортировки почты.
#!/usr/bin/perl  my(@msgs, @sub); my $msgno = -1; $/ = '';                    # считывание абзацами while (<>) {     if (/^From /m) {         /^Subject:\s*(?:Re:\s*)*(.*)/mi;         $sub[++$msgno] = lc($1) || '';     }     $msgs[$msgno] .= $_; } for my $i (sort { $sub[$a] cmp $sub[$b] || $a <=> $b } (0 .. $#msgs)) {     print $msgs[$i]; }
Или более коротко
#!/usr/bin/perl -n00 # bysub2 - сортировка по теме сообщения в духе awk BEGIN { $msgno = -1 } $sub[++$msgno] = (/^Subject:\s*(?:Re:\s*)*(.*)/mi)[0] if /^From/m; $msg[$msgno] .= $_; END { print @msg[ sort { $sub[$a] cmp $sub[$b] || $a <=> $b } (0 .. $#msg) ] }

Как узнать имя узла, домена, IP-адрес?

(Отвечает Брайан Д. Фой)
Модуль Net::Domain, входящий в стандартную поставку, начиная с perl5.7.3, может определить полностью уточнённое доменное имя (FQDN), имя узла или домена.
use Net::Domain qw(hostname hostfqdn hostdomain);  my $host = hostfqdn();
Мудуль Sys::Hostname, входящий в стандартную поставку, начиная с perl5.6, также может получить имя узла.
use Sys::Hostname;  $host = hostname();
Для получения IP-адреса можно использовать встроенную функцию gethostbyname, преобразующую имя в число. Для преобразования этого числа в привычную последовательность разделенных точкой октетов (a.b.c.d) используйте функцию inet_ntoa из модуля Socket, который тоже прилагается к perl.
use Socket;  my $address = inet_ntoa(     scalar gethostbyname( $host || 'localhost' )     );

Как скачать сообщение из группы новостей или список активных групп?

Используйте Net::NNTP или News::NNTPClient из CPAN. С ними задачи вроде получения списка групп новостей решаются очень просто:
perl -MNews::NNTPClient   -e 'print News::NNTPClient->new->list("newsgroups")'

Как скачать/закачать файл по FTP?

С LWP::Simple (есть в CPAN) можно скачать файл, но не закачать. Net::FTP (тоже есть в CPAN) более сложный, но поддерживает и закачивание, и скачивание.

Как в Perl делать удаленный вызов процедур (RPC)?

(Отвечает Брайан Д. Фой)
Используйте один из модулей CPAN для RPC: http://search.cpan.org/search?query=RPC&mode=all.

perlfaq6 — регулярные выражения http://beshenov.ru/perlfaq/6.html

http://beshenov.ru/perlfaq/6.html

perlfaq6 — регулярные выражения

Этот раздел оказался неожиданно маленьким, потому что остальной FAQ забит ответами, использующими регулярные выражения. Например, декодирование URL или проверка, является ли скаляр числом, проводится с регулярными выражениями, но это описано в других разделах.
Как использовать регулярные выражения, не создавая нечитаемый и неподдерживаемый код?
Шаблон не работает больше чем с одной строкой текста. Что не так?
Как вырезать строки между двумя шаблонами, которые сами стоят на разных строках?
Как регулярными выражениями осуществлять поиск по XML, HTML и прочим ужасным вещам?
Я занес в $/ регулярное выражение, но это не работает. Что не так?
Как сделать нечувствительную к регистру подстановку, чтобы результат сохранял искомый регистр?
Как заставить \w соответствовать национальной кодировке?
Как использовать в шаблоне соответствующую локальным установкам версию /[a-zA-Z]/?
Как экранировать переменную для использования в регулярном выражении?
Зачем нужно /o?
Как при помощи регулярных выражений удалить из кода комментарии в стиле C?
Как использовать регулярные выражения Perl для поиска по сбалансированному тексту?
Что подразумевается под «жадными регулярными выражениями»? Как их сделать нежадными?
Как обработать каждое слово в каждой строке?
Как сделать сводку по частоте вхождения слов или строк?
Как сделать нечеткий поиск по шаблону?
Как сделать эффективный поиск сразу по нескольким шаблонам?
Почему не работает поиск с разделителем слов \b?
Почему использование $&, $` и $' замедляет программу?
Зачем в регулярном выражении \G?
Регулярные выражения в Perl — это ДКА или НКА? Они POSIX-совместимы?
Почему не следует использовать grep в пустом контексте?
Как сделать поиск по строке с многобайтовыми символами?
Как осуществить поиск по шаблону, записанному в переменной?

Как использовать регулярные выражения, не создавая нечитаемый и неподдерживаемый код?

Для создания поддерживаемых и понятных регулярных выражений можно использовать три приема.

Комментарии вне регулярного выражения

Описывайте обычными комментариями Perl, что и как делаете.
# Заменить строку на первое слово, двоеточие и # количество символов до конца s/^(\w+)(.*)/ lc($1) . ":" . length($2) /meg;

Комментарии внутри регулярного выражения

Модификатор /x включает пропуск пробельных символов в шаблоне (кроме пробелов в классе символов), а также позволяет использовать обычные комментарии. Это сильно помогает.
/x позволяет превратить шаблон
s{<(?:[^>'"]*|".*?"|'.*?')+>}{}gs;
в следующий:
s{ <                 # открывающая угловая скобка     (?:                 # группирующие скобки, не порождающие обратную ссылку         [^>'"] *            # любой символ, кроме > ' ", 0 и более раз             |                   #    ИЛИ         ".*?"               # подстрока между двойными кавычками (нежадный поиск)             |                   #    ИЛИ         '.*?'               # подстрока между одинарными кавычками (нежадный поиск)     ) +                 #    всё встречается один и более раз     >                   # закрывающая угловая скобка }{}gsx;              # заменить пустой строкой, то есть, удалить
Это не столь же понятно, как обычный текст, но очень полезно при описании значения каждой части шаблона.

Различные разделители

Хотя обычно шаблоны заключаются в символы /, можно использовать практически любой разделитель — смотрите perlre. Например, выше использовались фигурные скобки. Иногда вместо того, чтобы экранировать символы в шаблоне, лучше использовать другой разделитель:
s/\/usr\/local/\/usr\/share/g;    # разделитель выбран неудачно s#/usr/local#/usr/share#g;        # это лучше

Шаблон не работает больше чем с одной строкой текста. Что не так?

Либо в рассматриваемой переменной только одна строка, либо вы используете неправильные модификаторы шаблона.
Записать в переменную несколько строк можно несколькими способами. Если хотите сделать это автоматически при считывания ввода, то следует установить $/ таким образом, чтобы позволить считывать за раз больше одной строки (например, задать '' для считывания абзацами и undef — для считывания файла целиком).
Посмотрите perlre, чтобы решить, что следует использовать: /s/m, либо оба модификатора. /s допускает совпадение . с переводом строки, а /m допускает соответствие ^ и $ границам строк, а не только началу и концу переменной. При этом вы должны убедиться, что в переменной действительно содержится несколько строк.
Например, эта программа определяет повторяющиеся слова, даже если повторы распространяются за переводы строк (но не параграфов). Здесь не требуется /s, потому что мы не используем в регулярном выражении символ ., который мог бы соответствовать переводу строки. Не требуется и /m, потому что не нужно, чтобы ^ и $ соответствовали границам внутренних строк. Но $/ обязательно нужно задать значение, отличное от значения по умолчанию, иначе мы не будем считывать многострочные записи.
$/ = '';          # считывать абзацами, а не строками while ( <> ) {     while ( /\b([\w'-]+)(\s+\1)+\b/gi ) {      # слово начинается с буквы         print "Повтор $1 в абзаце $.\n";     } }
Вот код для поиска строк, начинающихся с «From » (что будет искажено большинством почтовых программ):
$/ = '';          # считывать абзацами, а не строками while ( <> ) {     while ( /^From /gm ) { # /m заставляет ^ соответствовать началу внутренней строки в $_     print "в абзаце $ есть строка с началом 'From'.\n";     } }
Вот код для поиска в абзаце текста между START и END:
undef $/;          # считать файл целиком, а не строками или абзацами while ( <> ) {     while ( /START(.*?)END/sgm ) { # /s заставляет . переходить через границы строк         print "$1\n";     } }

Как вырезать строки между двумя шаблонами, которые сами стоят на разных строках?

Можно использовать странный оператор .., задокументированный в perlop:
perl -ne 'print if /START/ .. /END/' file1 file2 ...
Если нужен текст, а не строки, то используйте
perl -0777 -ne 'print "$1\n" while /START(.*?)END/gs' file1 file2 ...
Но если нужно обработать вложенные блоки START-END, то вы столкнетесь с задачей поиска по сбалансированному тексту, описанной в другом вопросе FAQ.
Вот другой пример использования ..:
while (<>) {     $in_header =   1  .. /^$/;     $in_body   = /^$/ .. eof; # теперь выбираем из них } continue {     $. = 0 if eof;    # поправить $. }

Как регулярными выражениями осуществлять поиск по XML, HTML и прочим ужасным вещам?

(Отвечает Брайан Д. Фой)
Если нужно просто решить задачу, то используйте тот или иной модуль и забудьте про регулярные выражения. Можно начать с XML::Parser и HTML::Parser, хотя каждое пространство имен содержит другие модули разбора, заточенные под особые задачи и методы решения. Откройте поиск по CPAN, и вы будете удивлены тем, что всю работу за вас уже сделали :-)
Языки вроде XML сложно разбирать, потому что они содержат сбалансированный текст с несколькими уровнями вложенности, но некоторые элементы сбалансированным текстом не являются — например, пустой тег (вроде <br/>). И даже если вы думаете, что ваш шаблон подходит для входного текста, могут встречаться непредвиденные ситуации.
Если хотите пойти по сложному пути, продираться к правильному решению, постоянно натыкаться на неудачи, быть заваленным сообщениями об ошибках, мучаться огромное количество времени, которое придется тратить на изобретение кривого колеса, то перед тем, как окончательно сдаваться, нужно
решить задачу сбалансированного текста из другого вопроса perlfaq6;
попробовать рекурсивные регулярные выражения, появившиеся в Perl 5.10 (см. perlre);
попробовать определить грамматику при помощи (?DEFINE) из Perl 5.10;
разбить задачу на составные и не пытаться написать единое регулярное выражение;
уговорить всех не использовать первым делом XML или HTML.
Удачи!

Я занес в $/ регулярное выражение, но это не работает. Что не так?

В $/ должна быть строка. Если действительно нужно, то можете использовать следующие примеры.
Если есть File::Stream, то всё просто.
use File::Stream;  my $stream = File::Stream->new(     $filehandle,     separator => qr/\s*,\s*/,     );  print "$_\n" while <$stream>;
Если File::Stream нет, то нужно сделать немного больше.
Можно использовать вариант вызова sysread с четырьмя аргументами, чтобы постоянно дописывать в буфер. После добавления в буфер проверяйте при помощи регулярного выражения, имеется ли полная строка.
local $_ = ""; while( sysread FH, $_, 8192, length ) {     while( s/^((?s).*?)your_pattern/ ) {         my $record = $1;         # ...     } }
То же самое можно сделать с foreach, поиском по шаблону с флагом c и якорем \G, если вы не против, чтобы под конец в память был занесен весь файл.
local $_ = ""; while( sysread FH, $_, 8192, length ) {     foreach my $record ( m/\G((?s).*?)your_pattern/gc ) {         # ...     } substr( $_, 0, pos ) = "" if pos; }

Как сделать нечувствительную к регистру подстановку, чтобы результат сохранял искомый регистр?

Вот красивое решение в духе Perl от Ларри Рослера. Оно использует свойства побитового исключающего ИЛИ для ASCII-строк.
$_= "this is a TEsT case";  $old = 'test'; $new = 'success';  s{(\Q$old\E)} { uc $new | (uc $1 ^ $1) .     (uc(substr $1, -1) ^ substr $1, -1) x     (length($new) - length $1) }egi;  print;
А вот процедура, созданная на основе кода выше:
sub preserve_case($$) {     my ($old, $new) = @_;     my $mask = uc $old ^ $old;      uc $new | $mask .         substr($mask, -1) x (length($new) - length($old)) }  $string = "this is a TEsT case"; $string =~ s/(test)/preserve_case($1, "success")/egi; print "$string\n";
Это выводит
this is a SUcCESS case
Если нужно сохранить регистр слова замены, когда оно длиннее исходного, можно использовать код Джеффа Пиньяна:
sub preserve_case {     my ($from, $to) = @_;     my ($lf, $lt) = map length, @_;      if ($lt < $lf) { $from = substr $from, 0, $lt }     else { $from .= substr $to, $lf }      return uc $to | ($from ^ uc $from);     }
Это заменяет строку на «this is a SUcCess case».
Если вам больше нравятся решения в духе C, то следующий скрипт показывает, как программирующие на C могут создавать код C на любом языке, и делает абсолютно ту же подстановку. (А еще это работает примерно на 240% медленнее, чем код в духе Perl.) Если в подставляемом слове больше символов, чем в заменяемом, то для оставшейся части используется регистр последнего символа.
# Код Натана Торкингтона, переделанный Джефри Фридлом # sub preserve_case($$) {     my ($old, $new) = @_;     my ($state) = 0;    # 0 = без изменений; 1 = lc; 2 = uc     my ($i, $oldlen, $newlen, $c) = (0, length($old), length($new));     my ($len) = $oldlen < $newlen ? $oldlen : $newlen;      for ($i = 0; $i < $len; $i++) {         if ($c = substr($old, $i, 1), $c =~ /[\W\d_]/) {             $state = 0;         } elsif (lc $c eq $c) {             substr($new, $i, 1) = lc(substr($new, $i, 1));             $state = 1;         } else {             substr($new, $i, 1) = uc(substr($new, $i, 1));             $state = 2;         }     }     # завершить оставшейся частью $new (если $new длиннее $old)     if ($newlen > $oldlen) {         if ($state == 1) {             substr($new, $oldlen) = lc(substr($new, $oldlen));         } elsif ($state == 2) {             substr($new, $oldlen) = uc(substr($new, $oldlen));         }     }     return $new; }

Как заставить \w соответствовать национальной кодировке?

Добавьте в скрипт use locale;, чтобы класс символов \w брался из текущих локальных установок.
Подробности см. в perllocale.

Как использовать в шаблоне соответствующую локальным установкам версию /[a-zA-Z]/?

Можете использовать синтаксис POSIX для классов символов /[[:alpha:]]/, задокументированный в perlre.
Вне зависимости от локальных установок, символы алфавита содержатся в \w за вычетом цифр и подчеркивания. Это соответствует шаблону /[^\W\d_]/. Его дополнение, неалфавитные символы — всё в \W, а также цифры и подчеркивание, то есть /[\W\d_]/.

Как экранировать переменную для использования в регулярном выражении?

Парсер Perl раскрывает $variable и @variable в регулярном выражении, если только в качестве разделителя не использована одиночная кавычка. Также помните, что в подстановке s/// шаблон замены считается строкой в двойных кавычках (подробности см. в perlop). Кроме того, все специальные символы для регулярных выражений сохраняют силу, если не используется экранирование \Q. Например:
$string = "Placido P. Octopus"; $regex  = "P.";  $string =~ s/$regex/Polyp/; # теперь в $string записано "Polypacido P. Octopus"
Так как . — специальный символ в регулярных выражениях, соответствующий любому символу, то шаблон P.совпал с "Pl" в исходной строке.
Для экранирования . используется \Q:
$string = "Placido P. Octopus"; $regex  = "P.";  $string =~ s/\Q$regex/Polyp/; # теперь в $string записано "Placido Polyp Octopus"
\Q приводит к тому, что "." считается простым символом, и P. соответствует "P" с точкой.

Зачем нужно /o?

(Отвечает Брайан Д. Фой)
Опция /o для регулярных выражений (задокументирована в perlop и perlreref) указывает, чтобы регулярное выражение компилировалось единожды. Это полезно, только если шаблон включает переменную. Начиная с Perl 5.6, такое происходит автоматически, если шаблон не меняется.
Так как оператор поиска m//, подстановки s///, а также оператор экранирования регулярного выраженияqr// — контексты двойных кавычек, то переменные можно интерполировать в шаблон. Подробности см. в ответе на вопрос «Как экранировать переменную для использования в регулярном выражении?»
Следующий пример принимает регулярное выражение из списка аргументов и печатает строки ввода, соответствующие ему:
my $pattern = shift @ARGV;  while( <> ) {     print if m/$pattern/;     }
Версии Perl до 5.6 могли перекомпилировать регулярное выражение на каждой итерации, даже если $patternне менялся. /o должен предотвратить это: шаблон будет компилироваться только первый раз, а потом использоваться в последующем.
my $pattern = shift @ARGV;  while( <> ) {     print if m/$pattern/o;    # полезно для Perl < 5.6     }
Начиная с версии 5.6, perl не будет перекомпилировать регулярное выражение, если переменная не меняется, поэтому /o скорее не пригодится: не навредит, но и не поможет. Используйте /o в любой версии Perl, если хотите, чтобы шаблон не перекомпилировался, даже если переменная меняется (то есть, чтобы использовалось первоначальное значение).
Чтобы сделать для себя проверку, проходит ли перекомпиляция регулярного выражения, можете посмотреть на движок регулярных выражений Perl в действии. Прагма use re 'debug' (введена в Perl 5.005) показывает подробности. В Perl до версии 5.6 вы должны видеть сообщения о перекомпиляции от re на каждой итерации. Начиная с Perl 5.6, вы должны видеть сообщение от re только для первой итерации.
use re 'debug';  $regex = 'Perl'; foreach ( qw(Perl Java Ruby Python) ) {     print STDERR "-" x 73, "\n";     print STDERR "Trying $_...\n";     print STDERR "\t$_ is good!\n" if m/$regex/;     }

Как при помощи регулярных выражений удалить из кода комментарии в стиле C?

Хотя это возможно, это намного сложней, чем кажется. Так, однострочник
perl -0777 -pe 's{/\*.*?\*/}{}gs' foo.c
будет работать во многих, но не во всех случаях. Это слишком просто для отдельных программ на C, в частности, тех, которые содержат «комментарии» внутри строк. В этом случае нужно нечто вроде скрипта, созданного Джефри Фридлом и доработанного Фредом Кёртисом.
$/ = undef; $_ = <>; s#/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse; print;
Конечно, это можно записать более понятно с модификатором /x, если отформатировать шаблон и расставить комментарии. Вот развернутая запись, любезно предоставленная Фредом Кёртисом.
s{    /\*         ##  Начало комментария /* ... */    [^*]*\*+    ##  Отличный от * символ с последующим 1 и более *    (      [^/*][^*]*\*+    )*          ##  0 и более подстрок, не начинающихся с /                ##    но заканчивающихся на '*'    /           ##  Конец комментария /* ... */   |         ##     ИЛИ  разные вещи, не являющиеся комментариями:     (      "           ##  Начало строки " ... "      (        \\.           ##  Экранированный символ      |               ##    ИЛИ        [^"\\]        ##  Не "\      )*      "           ##  Конец строки " ... "     |         ##     ИЛИ       '           ##  Начало строки ' ... '      (        \\.           ##  Экранированный символ      |               ##    ИЛИ        [^'\\]        ##  Не '\      )*      '           ##  Конец строки ' ... '     |         ##     ИЛИ       .           ##  Любой другой символ      [^/"'\\]*   ##  Символы, с которых не начинается комментарий, строка, экранированный символ    )  }{defined $2 ? $2 : ""}gxse;
Небольшая доработка также удаляет комментарии C++, не продолжающиеся на нескольких строках при помощи символа продолжения строки:
s#/\*[^*]*\*+([^/*][^*]*\*+)*/|//[^\n]*|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;

Как использовать регулярные выражения Perl для поиска по сбалансированному тексту?

(Отвечает Брайан Д. Фой)
Сначала следует попробовать модуль Text::Balanced, который входит в стандартную библиотеку Perl, начиная с версии 5.8. В Text::Balanced есть много функций по работе с хитрым текстом. МодульRegexp::Common также может помочь готовыми шаблонами.
Начиная с Perl 5.10, можно осуществлять поиск по сбалансированному тексту при помощи рекурсивных шаблонов. До 5.10 приходилось прибегать к разным уловкам вроде включения кода Perl в последовательности (??{}).
Вот пример использования рекурсивного регулярного выражения. Задача — захватить весь текст в угловых скобках, в том числе текст во вложенных угловых скобках. Текст примера содержит две основные группы: с одним уровнем вложенности и с двумя уровнями вложенности. Всего существует пять групп в угловых скобках:
I have some <brackets in <nested brackets> > and <another group <nested once <nested twice> > > and that's it.
Регулярное выражения для поиска по сбалансированному тексту использует новые возможности Perl 5.10, описанные в perlre. Этот пример основан на том, который дается в документации.
Во-первых, добавление + к любому квалификатору ищет самое длинное совпадение без обратного поиска. Это важно, потому что требуется обработать все угловые скобки по рекурсии, а не перебором с возвратами. Группа [^<>]++ ищет один и более символ, отличный от угловой скобки, не привлекая обратный поиск.
Во-вторых, новая конструкция (?PARNO) соответствует подшаблону буфера захвата, определенного PARNO. В следующем регулярном выражении первый буфер захвата ищет (и сохраняет) сбалансированный текст и требуется, чтобы тот же шаблон был применен к содержимому первого буфера для обработки вложенного текста. В этом состоит рекурсия. (?1) использует шаблон во внешнем буфере захвата в качестве независимой части регулярного выражения.
В результате имеем
#!/usr/local/bin/perl5.10.0  my $string =<<"HERE"; I have some <brackets in <nested brackets> > and <another group <nested once <nested twice> > > and that's it. HERE  my @groups = $string =~ m/         (                   # начало буфера захвата 1         <                   # открывающая угловая скобка             (?:                 [^<>]++     # один и более символов, отличных от угловой скобки; без обратного поиска                   |                 (?1)        # встретили < или >, рекурсивно обращаемся к буферу захвата 1             )*         >                   # закрывающая угловая скобка         )                   # конец буфера захвата 1         /xg;  $" = "\n\t"; print "Found:\n\t@groups\n";
Вывод показывает, что Perl нашел две большие группы:
Found:     <brackets in <nested brackets> >     <another group <nested once <nested twice> > >
С небольшой доработкой, можно найти все группы в угловых скобках, даже если они заключены в другие угловые скобки. Каждый раз при нахождении сбалансированного текста удаляйте его внешний разделитель (вы уже нашли его, поэтому не должны встретить повторно) и добавляйте в очередь строк для обработки. Делайте это, пока не получите 0 совпадений:
#!/usr/local/bin/perl5.10.0  my @queue =<<"HERE"; I have some <brackets in <nested brackets> > and <another group <nested once <nested twice> > > and that's it. HERE  my $regex = qr/         (                   # начало скобки 1         <                   # открывающая угловая скобка             (?:                 [^<>]++     # один и более символов, отличных от угловой скобки; без обратного поиска                   |                 (?1)        # рекурсивно обращаемся к скобке 1             )*         >                   # закрывающая угловая скобка         )                   # конец скобки 1         /x;  $" = "\n\t";  while( @queue )     {     my $string = shift @queue;      my @groups = $string =~ m/$regex/g;     print "Found:\n\t@groups\n\n" if @groups;      unshift @queue, map { s/^<//; s/>$//; $_ } @groups;     }
Вывод отображает все группы. Сначала выводятся внешние вхождения, а потом уже — вложенные:
Found:     <brackets in <nested brackets> >     <another group <nested once <nested twice> > >  Found:     <nested brackets>  Found:     <nested once <nested twice> >  Found:     <nested twice>

Что подразумевается под «жадными регулярными выражениями»? Как их сделать нежадными?

Обычно имеется в виду, что выражение соответствует как можно большей строке. Строго говоря, имеются квантификаторы (?*+{}), которые являются более жадными, чем весь шаблон; в Perl предпочтительна локальная жадность и выбор ближайшего соответствия, а не жадность всего шаблона. Чтобы получить нежадные версии тех же квантификаторов, используйте (??*?+?{}?).
Пример:
$s1 = $s2 = "I am very very cold"; $s1 =~ s/ve.*y //;      # I am cold $s2 =~ s/ve.*?y //;     # I am very cold
Заметьте, что во второй подстановке поиск закончился сразу при встрече "y ". Квантификатор *? указывает движку регулярных выражений найти соответствие как можно быстрее и не проверять, что стоит дальше в строке. Это похоже на детскую игру «горячая картошка».

Как обработать каждое слово в каждой строке?

Используйте функцию split:
while (<>) {     foreach $word ( split ) {         # сделать что-то с $word     } }
Заметьте, что это не слова в обычном понимании, а просто последовательности непробельных символов. Для обработки только буквенно-цифровых последовательностей (с символами подчеркивания) используйте
while (<>) {     foreach $word (m/(\w+)/g) {         # сделать что-то с $word     } }

Как сделать сводку по частоте вхождения слов или строк?

Чтобы это сделать, нужно разобрать каждое слово во входном потоке. Пусть под словом понимается последовательность букв, дефисов и апострофов, а не просто непробельные символы, как в предыдущем вопросе:
while (<>) {     while ( /(\b[^\W_\d][\w'-]+\b)/g ) {   # упускает варианты вроде "`sheep'"         $seen{$1}++;     } }  while ( ($word, $count) = each %seen ) {     print "$count $word\n";     }
Если нужно сделать то же со строками, то регулярные выражения не нужны:
while (<>) {     $seen{$_}++;     }  while ( ($line, $count) = each %seen ) {     print "$count $line"; }
Если хотите сделать вывод в отсортированном порядке, то смотрите в perlfaq4 вопрос о сортировке хеша.

Как сделать нечеткий поиск по шаблону?

См. модуль String::Approx из CPAN.

Как сделать эффективный поиск сразу по нескольким шаблонам?

(Отвечает Брайан Д. Фой)
Избегайте компиляции регулярного выражения при каждом обращении к нему. В примере ниже perl должен перекомпилировать регулярное выражение при каждой итерации foreach, так как не может знать, что будет в$pattern.
@patterns = qw( foo bar baz );  LINE: while( <DATA> )     {     foreach $pattern ( @patterns )         {         if( /\b$pattern\b/i )             {             print;             next LINE;             }         }     }
В Perl 5.005 введен оператор qr//. Он компилирует, но не применяет регулярное выражение. Если вы используете предварительно скомпилированное регулярное выражение, то perl делает меньше затрат. Здесь добавлен map, чтобы каждый шаблон переходил в заранее скомпилированный вид. Остальная часть скрипта та же, но работать будет быстрее.
@patterns = map { qr/\b$_\b/i } qw( foo bar baz );  LINE: while( <> )     {     foreach $pattern ( @patterns )         {         if( /$pattern/ )             {             print;             next LINE;             }         }     }
В некоторых случаях можно свести несколько шаблонов в одно регулярное выражение. Однако избегайте ситуаций, требующих обратного поиска.
$regex = join '|', qw( foo bar baz );  LINE: while( <> )     {     print if /\b(?:$regex)\b/i;     }
Подробности насчет эффективности регулярных выражений смотрите в «Mastering Regular Expressions» Джефри Фридла. Там описано устройство движка регулярных выражений, и почему некоторые шаблоны удивительно неэффективны. Если вы будете понимать, как регулярные выражения работают в perl, вы сможете адаптировать их для конкретных случаев.

Почему не работает поиск с разделителем слов \b?

(Отвечает Брайан Д. Фой)
Убедитесь, что вы знаете, что на самом деле делает \b: это разделитель между символом слова \w и чем-то, что не является символом слова — это может быть \W, а также начало или конец строки.
Это не разделитель между пробельным и непробельным символом и не то, что разделяет слова в привычном понимании.
В терминах регулярных выражений, разделитель слов \b — «zero width assertion», он представляет не символ строки, а выполнение определенного условия в данной позиции.
В регулярном выражении /\bPerl\b/ требуется, чтобы разделитель слов был до "P" и после "l". Если до "P" и после "l" стоит что-то помимо \w, шаблон совпадет. Следующие строки соответствуют /\bPerl\b/:
"Perl"    # перед P или после l не стоит символ слова "Perl "   # аналогично, пробел — не символ слова "'Perl'"  # ' — не символ слова "Perl's"  # перед P нет символа слова, после l стоит не символ слова
Следующие строки не соответствуют /\bPerl\b/.
"Perl_"   # _ — символ слова! "Perler"  # перед P символа слова нет, но есть после l
Однако для поиска слов не нужно использовать \b. Можно рассмотреть не-символы слова, окруженные символами слова. Такие строки соответствуют шаблону /\b'\b/.
"don't"   # символ ' заключен между "n" и "t" "qep'a'"  # символ ' заключен между "p" и "a"
Следующие строки не соответствуют /\b'\b/.
"foo'"    # после ' нет символа слова
Также можно использовать \B — дополнение \b, чтобы указать, что разделитель слов не должен встречаться.
В шаблоне /\Bam\B/, до "a" и после "m" должен быть символ слова. Следующие строки соответствуют/\Bam\B/:
"llama"   # "am" окружено символами слова "Samuel"  # аналогично
Следующие строки не соответствуют /\Bam\B/
"Sam"      # до "a" нет ограничителя слова, но есть после "m" "I am Sam" # "am" окружено не-символами слова

Почему использование $&, $` и $' замедляет программу?

(Отвечает Анно Зигель)
Если Perl видит, что где-либо в программе вам требуется одна из этих переменных, то он обеспечивает их для каждого поиска по шаблону. То есть, всякий раз копируется вся строка, частично — в $`, частично — в $&, частично — в $'. Это влечет серьезные неприятности, если вы имеете дело с большими строками или часто обращаетесь к шаблонам. Избегайте использования $&$' и $`, если можете; если не можете, то, использовав единожды, не стесняйтесь использовать где угодно, потому что уже заплатили за это. В некоторых алгоритмах эти переменные действительно полезны. Начиная с версии 5.005, $& уже не такая затратная, как две остальные.
Начиная с Perl 5.6.1, специальные переменные @- и @+ могут заменить $`$& и $'. Эти массивы содержат указатели на начало и конец каждого совпадения (подробности см. в perlvar) — дают ту же информацию, но без риска избыточного копирования строк.
В Perl 5.10 добавились ${^MATCH}${^PREMATCH} и ${^POSTMATCH} для тех же целей, но без общего ущерба для производительности. Perl задает эти переменные, только если регулярное выражение компилируется или выполняется с модификатором /p.

Зачем в регулярном выражении \G?

Якорь \G используется для начала следующего поиска по той же строке в том месте, где произошла остановка. С этим якорем движок регулярных выражений не может пропустить какие-либо символы для поиска следующего совпадения, в этом \G похож на якорь начала строки ^\G обычно используется с флагомg. Он использует значение pos() в качестве позиции для начала следующего поиска. Как только оператор поиска по шаблону находит совпадение, он заносит в pos() позицию следующего за совпадением символа (или первого символа следующего совпадения, это как посмотреть). Каждая строка имеет свое значениеpos().
Пусть нужно найти последовательно все пары цифр в строке вида "1122a44" и прекратить поиск, как только встретится не-цифра. Требуется извлечь 11 и 22; между 22 и 44 встречается буква a и на ней нужно остановиться. Простой поиск пар цифр пропускает a и находит 44.
$_ = "1122a44"; my @pairs = m/(\d\d)/g;   # qw( 11 22 44 )
Если вы используете якорь \G, то требуете, чтобы совпадение после 22 начиналось с a. Регулярное выражение не может дать такое совпадение, потому что в нем нет цифры; поиск прекращается, и оператор возвращает уже найденные пары.
$_ = "1122a44"; my @pairs = m/\G(\d\d)/g; # qw( 11 22 )
Можете также использовать якорь \G в скалярном контексте. Также потребуется флаг g.
$_ = "1122a44"; while( m/\G(\d\d)/g )     {     print "Found $1\n";     }
После того, как поиск обрывается на букве a, perl сбрасывает pos(), и следующий поиск по той же строке начнется сначала.
$_ = "1122a44"; while( m/\G(\d\d)/g )     {     print "Found $1\n";     }  print "Found $1 after while" if m/(\d\d)/g; # находит "11"
Сброс pos() по окончанию поиска можно отключить флагом c, задокументированном в perlop и perlreref. Следующий поиск начинается там, где закончилось предыдущее совпадение (в позиции pos()), даже если поиск по той же строке завершился. В этом случае поиск после цикла while() начнется с a (там, где завершился последний), и, так как в нем не используется \G, то a будет пропущено и мы получим 44.
$_ = "1122a44"; while( m/\G(\d\d)/gc )     {     print "Found $1\n";     }  print "Found $1 after while" if m/(\d\d)/g; # находит "44"
Обычно якорь \G используется вместе с флагом c, когда нужно попробовать другой шаблон, если какой-то не подошел, как это делается в разборе по лексемам. Следующий пример Джефри Фридла работает, начиная с Perl 5.004.
while (<>) {     chomp;     PARSER: {         m/ \G( \d+\b    )/gcx   && do { print "number: $1\n";  redo; };         m/ \G( \w+      )/gcx   && do { print "word:   $1\n";  redo; };         m/ \G( \s+      )/gcx   && do { print "space:  $1\n";  redo; };         m/ \G( [^\w\d]+ )/gcx   && do { print "other:  $1\n";  redo; };     } }
Для каждой строки ввода цикл PARSER сначала пытается найти последовательность цифр, завершающуюся ограничителем слова. Поиск должен начинаться там, где закончился предыдущий (первый — с начала строки). Так как в m/ \G( \d+\b )/gcx использован флаг c, то, если строка не соответствует регулярному выражению, perl не сбрасывает pos(), и следующий поиск начинается в той же позиции, чтобы попробовать другую лексему.

Регулярные выражения в Perl — это ДКА или НКА? Они POSIX-совместимы?

Хотя регулярные выражения в Perl похожи на детерминированные конечные автоматы из программыegrep(1), на самом деле они реализованы как недетерминированные конечные автоматы, чтобы имелась возможность использовать поиск с возвратом (backtracking) и обратные ссылки (backreferencing). Кроме того, они не POSIX-совместимы, потому что POSIX-совместимые выражения гарантируют наихудшее поведение во всех случаях. (Некоторые предпочитают последовательность в работе, даже если она сводится к медленной скорости.) За всеми мыслимыми подробностями по теме обратитесь к книге Джефри Фридла «Mastering Regular Expressions» (выходила в издательстве O'Reilly, полная ссылка есть в perlfaq2).

Почему не следует использовать grep в пустом контексте?

grep создает список для возврата независимо от контекста; вы заставляете Perl создавать список, а потом просто отбрасывать. Если список большой, то вы расходуете время и память. Если хотите пройти по списку, то используйте цикл for.
До perl 5.8.1 такая же проблема была у map, но позже это было исправлено, и map контекстно-зависима; в пустом контексте никаких списков не создается.

Как сделать поиск по строке с многобайтовыми символами?

Начиная с Perl 5.6, имеется какая-то поддержка многобайтовых символов. Рекомендуется версия 5.8 и более поздние. В модуле Encode поддерживается Юникод и традиционные кодировки. См. perluniintroperlunicode иEncode.
Если вы продолжаете работать со старыми версиями Perl, то Юникод поддерживается через модульUnicode::String и преобразование символов в Unicode::Map8 и Unicode::Map. Если вы используете японские кодировки, попробуйте jperl 5.005_03.
Наконец, следующие приемы предложены Джефри Фридлом, чья статья в 5 номере «Perl Journal» подробно рассматривает вопрос.
Пусть имеется странная марсианская кодировка, где пары заглавных букв ASCII обозначают одну марсианскую (так, одну марсианскую букву составляют пары байт "CV""SG""VS""XX", и так далее). Остальные байты представляют однобайтовые символы, как в обычном ASCII.
Поэтому строка на марсианском языке "I am CVSGXX!" использует 12 байт для кодирования девяти символов'I'' ''a''m'' ''CV''SG''XX''!'.
Теперь пусть нужно найти один символ /GX/. Perl не знает марсианского, поэтому найдет два байта "GX" в строке "I am CVSGXX!", даже если такого символа там нет: так кажется из-за стыка "SG" и "XX", но там нет"GX". Это серьезная проблема.
Вот несколько способов обойти это. Все сложные.
# разделить соседние марсианские байты $martian =~ s/([A-Z][A-Z])/ $1 /g;  print "found GX!\n" if $martian =~ /GX/;
Или так:
@chars = $martian =~ m/([A-Z][A-Z]|[^A-Z])/g; # по сути это похоже на @chars = $text =~ m/(.)/g; # foreach $char (@chars) { print "found GX!\n", last if $char eq 'GX'; }
Или так:
while ($martian =~ m/\G([A-Z][A-Z]|.)/gs) {  # возможно, \G не требуется     print "found GX!\n", last if $1 eq 'GX';     }
Вот другой, чуть менее сложный способ Бенджамина Голдберга, использующий (?<!) (zero-width negative look-behind assertion).
print "found GX!\n" if    $martian =~ m/     (?<![A-Z])     (?:[A-Z][A-Z])*?     GX     /x;
Это успешно выполняется, если марсианский символ "GX" есть в строке. Если вам не нравится (?<!), можете заменить (?<![A-Z]) на (?:^|[^A-Z]).
Тут есть недостаток в размещении неподходящих строк в $-[0] и $+[0], но обычно это можно обойти.

Как осуществить поиск по шаблону, записанному в переменной?

(Отвечает Брайан Д. Фой)
Шаблоны не обязательно жестко задавать в операторе поиска или в чём-либо еще, работающим с регулярными выражениями. Можно сохранить шаблон в переменной для последующего использования.
Оператор поиска соответствует контексту двойных кавычек, поэтому переменную можно интерполировать, как и в строке, заключенной в двойные кавычки. Ниже регулярное выражение считывается как пользовательский ввод и сохраняется в переменной $regex, которая затем используется в операторе поиска по шаблону.
chomp( my $regex = <STDIN> );  if( $string =~ m/$regex/ ) { ... }
Все специальные символы регулярных выражений в $regex сохраняют свое значение, и шаблон должен быть валидным, иначе Perl сообщит об ошибке. Так, в следующем шаблоне есть открывающая скобка без закрывающей:
my $regex = "Unmatched ( paren";  "Two parens to bind them all" =~ m/$regex/;
Когда Perl компилирует регулярное выражение, он принимает открывающую скобку за начало запоминаемой группы. Когда закрывающей скобки не находится, выдается сообщение об ошибке:
Unmatched ( in regex; marked by <-- HERE in m/Unmatched ( <-- HERE  paren/ at script line 3.
Вы можете обойти это несколькими способами, в зависимости от ситуации. Если ни один символ не должен считаться специальным, то перед использованием строки экранируйте символы функцией quotemeta.
chomp( my $regex = <STDIN> ); $regex = quotemeta( $regex );  if( $string =~ m/$regex/ ) { ... }
Также это можно сделать напрямую в операторе поиска по шаблону с \Q и \E\Q обозначает начало экранирования специальных символов, а \E — конец (подробности см. в perlop).
chomp( my $regex = <STDIN> );  if( $string =~ m/\Q$regex\E/ ) { ... }
Кроме того, можно использовать оператор экранирования регулярного выражения qr// (подробности см. вperlop), он экранирует и компилирует шаблон. Также к шаблону можно применять флаги регулярных выражений.
chomp( my $input = <STDIN> );  my $regex = qr/$input/is;  $string =~ m/$regex/  # то же самое, что и m/$input/is;
Может оказаться полезным заключить блок в eval и перехватывать любые ошибки:
chomp( my $input = <STDIN> );  eval {     if( $string =~ m/\Q$input\E/ ) { ... }     }; warn $@ if $@;
или
my $regex = eval { qr/$input/is }; if( defined $regex ) {     $string =~ m/$regex/;     } else {     warn $@;     }