Межстрочный поиск
Проблема
Требуется использовать регулярные выражения для последовательности, состоящей из нескольких строк.
Специальные символы . (любой символ, кроме перевода строки), ^ (начало строки) и $ (конец строки),
кажется, не работают. Это может произойти при одновременном чтении нескольких записей или всего содержимого файла.
Решение
Воспользуйтесь модификатором /m, /s или обоими сразу. Модификатор /s разрешает совпадение с
переводом строки (обычно этого не происходит). Если последовательность состоит из нескольких строк,
шаблон /foo. *bar/s совпадет с foo" и "bar", находящимися в двух соседних строках. Это не относится
к точкам и символьных классах (например, [#%. ]), которые всегда представляют собой обычные точки.
Модификатор /m разрешает совпадение ^ и $ в переводах строк. Например, совпадение для
шаблона /^=head[1-7]$/m возможно не только в начале записи, но и в любом из внутренних переводов строк.
Комментарий
При синтаксическом анализе документов, в которых переводы строк не имеют значения, часто
используется «силовое» решение — файл читается по абзацам (а иногда даже целиком), после чего
происходит последовательное извлечение лексем. Для успешного межстрочного поиска необходимо, чтобы
символ . совпадал с переводом строки — обычно этого не происходит. Если в буфер читается сразу
несколько строк, вероятно, вы предпочтете, чтобы символы ^ и $ совпадали с началом и концом
внутренних строк, а не всего буфера.
Необходимо хорошо понимать, чем /m отличается от /s: первый заставляет ^ и $ совпадать на
внутренних переводах строк, а второй заставляет . совпадать с переводом строки. Эти модификаторы
можно использовать вместе, они не являются взаимоисключающими.
Фильтр из следующего примера удаляет теги HTML из всех файлов, переданных в @ARGV, и
отправляет результат в STDOUT. Сначала мы отменяем разделение записей, чтобы при каждой операции
чтения читалось содержимое всего файла. Если @ARGV содержит несколько аргументов, файлов
также будет несколько. В этом случае при каждом чтении передается содержимое всего файла.
Затем мы удаляем все открывающие и закрывающие угловые скобки и все, что находится между ними.
Мы не можем просто воспользоваться .* по двум причинам: во-первых, этот шаблон не учитывает
закрывающих угловых скобок, а во-вторых, он не поддерживает межстрочных совпадений.
Проблема решается применением .*? в сочетании с модификатором /s — по крайней мере, в данном случае.
# killtags - очень плохое удаление тегов HTML
undef $/; # При каждом чтении передается весь файл
while (<>) { # Читать по одному файлу
s/<.*?>//gs; # Удаление тегов (очень скверное)
print; # Вывод файла в STDOUT
}
Шаблон s/<[^>]*>//g работает намного быстрее, но такой подход наивен: он приведет к неправильной обработке
тегов в комментариях HTML или угловых скобок в кавычках (<IMG SRC="here.gif" ALT="<<Ooh la la! >>">).
Программа из следующего примера получает простой текстовый документ и ищет в начале абзацев строки
вида "Chapter 20: Better Living Through Chemisery". Такие строки оформляются заголовками HTML первого уровня.
Поскольку шаблон получился довольно сложным, мы воспользовались модификатором /х, который разрешает
внутренние пропуски и комментарии.
# headerfy: оформление заголовков глав в HTML
$/ ='';
while ( <> ) { # Получить абзац
s{
\А # Начало записи
( # Сохранить в $1
Chapter # Текстовая строка
\s+ # Обязательный пропуск
\d+ # Десятичное число
\s* # Необязательный пропуск
: # Двоеточие
.* # Все, кроме перевода строки, до конца строки
)
}{<Н1>$1</Н1>}gх;
print;
}
Если комментарии лишь затрудняют понимание, ниже тот же пример переписан короче:
s{\A(Chapter\s+\d+\s*:.*)}{<H1>$1</H1>}gxp
Возникает интересная проблема: в одном шаблоне требуется указывать как начало записи, так
и конец строки. Начало записи можно было бы определить с помощью ^, но символ $ должен
определять не только конец записи, но и конец строки. Мы добавляем модификатор /m, отчего
изменяется смысл как ^, так и $. Начало записи вместо определяется с помощью \А. Кстати говоря,
метасимвол \Z (хотя в нашем примере он не используется) совпадает с концом записи даже при наличии модификатора /m.
Следующий пример демонстрирует совместное применение /s и /m. На этот раз мы хотим,
чтобы символ ^ совпадал с началом любой строки абзаца, а точка — с переводом строки.
Эти модификаторы никак не связаны, и их совместное применение ничем не ограничено.
Стандартная переменная $. содержит число записей последнего прочитанного файла.
Стандартная переменная $ARGV содержит файл, автоматически открываемый при обработке <ARGV>.
$/=''; # Режим чтения абзацев
while (<ARGV>) {
while (m#^START(.*?)^END#sm) { # /s - совпадение . с переводом строки
# /m - совпадение ^ с началом внутренних строк
print "chunk $. in $ARGV has <<$1>>\n";
}
}
Если вы уже привыкли работать с модификатором /m, то ^ и $ можно заменить на \А и \Z.
Но что делать, если вы предпочитаете /s и хотите сохранить исходный смысл .? Воспользуйтесь
конструкцией [^\n]. Если вы не намерены использовать /s, но хотите иметь конструкцию, совпадающую с
любым байтом, сконструируйте символьный класс вида [\000-\377] или даже [\d\D]. Использовать [.\n]
нельзя, поскольку в символьных классах . не обладает особой интерпретацией.
См. также
Proverte kod v komentariyah gde pro list tam oshibki detskie
Оставить комментарий:
|
|