Регулярное выражение


При поиске выражения (a+a+)+a в строке aaaaa интерпретатор пойдёт приблизительно по следующему пути:

  1. aaaaa
  2. aaaa
  3. aaaaa

  4. aaa
  5. aaaaa
  6. aaaa
  7. aaaaa — и только тут, проверив все точки возврата, остановится.

При использовании ревнивого квантификатора будет выполнен только первый шаг алгоритма.

ru.wikipedia.org


Регулярные выражения (РВ) это, по существу, крошечный язык программирования, встроенный в Python и доступный при помощи модуля re. Используя его, вы указывается правила для множества возможных строк, которые вы хотите проверить; это множество может содержать английские фразы, или адреса электронной почты, или TeX команды, или все что угодно. С помощью РВ вы можете задавать вопросы, такие как «Соответствует ли эта строка шаблону?», или «Совпадает ли шаблон где-нибудь с этой строкой?». Вы можете также использовать регулярные выражения, чтобы изменить строку или разбить ее на части различными способами.

Шаблоны регулярных выражений компилируются в серии байт-кода, которые затем исполняются соответствующим движком написанным на C. Для продвинутого использования может быть важно уделять внимание тому, как движок будет выполнять данное регулярное выражение, и писать его так, чтобы получался байт-код, который работает быстрее. Оптимизация не рассматривается в этом документе, так как она требует от вас хорошего понимания внутренних деталей движка.

Язык регулярных выражений относительно мал и ограничен, поэтому не все возможные задачи по обработке строк можно сделать с помощью регулярных выражений. Также существуют задачи, которые можно сделать с помощью регулярных выражений, но выражения оказываются слишком сложными. В этих случаях может быть лучше написать обычный Python код, пусть он будет работать медленнее, чем разработанное регулярное выражение, но будет более понятен.


Простые шаблоны

Мы начнем с изучения простейших регулярных выражений. Поскольку регулярные выражения используются для работы со строками, мы начнем с наиболее распространенной задачи — соответствия символов.

За подробным объяснением технической стороны регулярных выражений (детерминированных и недетерминированных конечных автоматов) вы можете обратиться к практически любому учебнику по написанию компиляторов.

Соответствие символов

Большинство букв и символов соответствуют сами себе. Например, регулярное выражение test будет в точности соответствовать строке test (Вы можете включить режим без учета регистра, что позволит этому регулярному выражению также соответствовать Test или TEST, но об этом позже).

Из этого правила есть исключения; некоторые символы это специальные метасимволы, и сами себе не соответствуют. Вместо этого они указывают, что должна быть найдена некоторая необычная вещь, или влияют на другие части регулярного выражения, повторяя или изменяя их значение. Большая часть этого пособия посвящена обсуждению различных метасимволов и тому, что они делают.

Вот полный список метасимволов; их значения будут обсуждаться в остальной части этого HOWTO.

. ^ $ * + ? { [ ] | ( )

Первые метасимволы, что мы рассмотрим это [ и ]


. Они используются для определения класса символов, являющегося набором символов, с которыми вы ищите совпадение. Символы могут быть перечислены по отдельности, или в виде некоторого диапазона символов, обозначенного первым и последним символом, разделенных знаком '-'. Например, [abc] будет соответствовать любому из символов a, b или c; это то же самое, что выражение [a-c], использующее диапазон для задания того же множества символов. Если вы хотите сопоставить только строчные буквы, РВ будет иметь вид [a-z].

Метасимволы не активны внутри классов. Например, [akm$] будет соответствовать любому из символов 'a', 'k', 'm' или '$'. Знак '$' это обычно метасимвол (как видно из списка символов выше), но внутри класса символов он лишается своей особой природы.

Для того, чтобы находить соответствие символам вне этого класса, в начале класса добавляется символ '^'. Например, выражение [^5] соответствует любому символу, кроме ‘5’.

Пожалуй, наиболее важным является метасимвол обратной косой черты . Как и в строковых литералах Python, за бэкслешем могут следовать различные символы, обозначающие разные специальные последовательности. Он также используется для экранирования метасимволов, чтобы их можно было использовать в шаблонах; например, если нужно найти соответствие [


или , для того чтобы лишить их своей особой роли метасимволов, перед ним нужно поставить обратную косую черту: [ или \.

Некоторые из специальных последовательностей, начинающихся с '' представляют предопределенные наборы символов, часто бывающие полезными, такие как набор цифр, набор букв, или множества всего, что не является пробелами, символами табуляции и т. д. (whitespace). Следующие предопределенные последовательности являются их подмножеством. Полный список последовательностей и расширенных определений классов для Юникод-строк смотрите в последней части Regular Expression Syntax.

d
Соответствует любой цифре; эквивалент класса [0-9].
D
Соответствует любому нечисловому символу; эквивалент класса [^0-9].
s
Соответствует любому символу whitespace; эквивалент [ tnrfv].
S
Соответствует любому не-whitespace символу; эквивалент [^ tnrfv].
w
Соответствует любой букве или цифре; эквивалент [a-zA-Z0-9_].
W
Наоборот; эквивалент [^a-zA-Z0-9_].

Эти последовательности могут быть включены в класс символов. Например, [s,.] является характер класс, который будет соответствовать любому whitespace-символу или запятой или точке.

Последний метасимвол в этом разделе это '.'


. Он соответствует всем символам, кроме символа новой строки, но есть альтернативный режим (re.DOTALL), где это множество будет включать и его. '.' часто используется там, где вы хотите сопоставить «любой символ».

Повторяющиеся вещи

Возможность сопоставлять различные наборы символов это первое, что регулярные выражения могут сделать и что не всегда можно сделать строковыми методами. Однако, если бы это было единственной дополнительной возможностью, они бы не были так интересны. Другая возможность заключается в том, что вы можете указать какое число раз должна повторяться часть регулярного выражения.

Первый метасимвол для повторения это *. Он указывает, что предыдущий символ может быть сопоставлен ноль и более раз, вместо одного сравнения.

Например, ca*t будет соответствовать ct (0 символов a), cat (1 символ a), caaat (3 символа a), и так далее. Движок регулярных выражений имеет различные внутренние ограничения вытекающие из размера int типа для C, что не позволяет проводить ему сопоставление более 2 миллиардов символов ‘a’. (Надеюсь, вам это не понадобится).

Повторения, такие как * называют жадными (greedy); движок будет пытаться повторить его столько раз, сколько это возможно. Если следующие части шаблона не соответствуют, движок вернется назад и попытается попробовать снова с несколькими повторами символа.

Пошаговое рассмотрение какого-нибудь примера сделает объяснение более ясным. Давайте рассмотрим выражение a[bcd]*b


. Оно соответствует букве 'a', нулю или более символов из класса [bcd], и наконец, заключительной букве 'b'. Теперь представим себе сопоставление этого регулярного выражения строке abcbd. Вот как происходит сравнение поэтапно:

1. a — ‘a’ соответствует регулярному выражению
2. abcbd — движок сопоставляет [bcd]* на как можно большем числе символов, то есть до конца строки (поскольку все символы соответствуют классу в скобках [])
3. Провал — движок пытается сопоставить последний символ в регулярном выражении — букву b, но текущая позиция уже в конце строки, где нет никаких символов, так что он терпит неудачу.
4. abcb — вернулись назад, уменьшили на один символ сопоставление с [bcd]*
5. Провал — пытаемся снова найти b, но в конце только d
6. abc — снова возвращаемся назад, теперь [bcd]* это только bc
7. abcb — снова ищем последний символ регулярного выражения — b. Теперь он действительно находится на нужной позиции и мы добиваемся успеха

Итак, был достигнут конец РВ и сопоставление с ним дало abcb. Этот пример показал, как движок сначала забирается так далеко, как может, и, если не находит соответствия, возвращается назад, снова и снова работая с остатком регулярного выражения. Он будет делать так до тех пор, пока не получит ноль совпадений для [bcd]*


, и, если и тогда не получится совпадения, то заключит, что строка совсем не соответствует шаблону РВ.

Другой метасимвол повторения это +, повторяющий последовательность сравнения один или более раз. Обратите особое внимание на разницу между * и +. * требует соответствия необходимой части ноль или более раз, то есть повторяемое может и не присутствовать вовсе, а + требует, по крайней мере одно вхождение. Для аналогичного примера ca+t будет сопоставляться cat или, например, caaat, но никак не ct.

Есть еще два повторяющих спецификатора. Знак вопроса, ?, проверяющий наличие совпадения ноль или один раз. Например, home-?brew соответствует как homebrew, так и home-brew.

Наиболее полный повторяющий спецификатор это {m,n}, где m и n — целые числа. Этот определитель означает, что здесь должно быть не менее m и не более n повторений. Например, a/{1,3}b соответствует a/b, a//b и a///b. Это не может быть ab, строка в которой нет слэшей или a////b


, в которой их четыре.

Вы можете не задавать m или n, тогда для отсутствующего предполагается наиболее разумное значение. Опускание m означает, что нижний предел 0, опускание n предполагает верхним пределом бесконечность, но, как уже говорилось выше, последний ограничен памятью.

Читатели уже могли заметить, что все три остальных спецификатора могут быть выражены через последний. {0,} это то же, что *, {1,} эквивалентно +, и {0,1} может заменять знак ?.

Использование регулярных выражений

Теперь, когда мы рассмотрели несколько простых регулярных выражений, как мы можем использовать их в Python? Модуль re предоставляет интерфейс для регулярных выражений, что позволяет компилировать регулярные выражения в объекты, а затем выполнять с ними сопоставления.

Компиляция регулярных выражений

Регулярные выражения компилируются в объекты шаблонов, имеющие методы для различных операций, таких как поиск вхождения шаблона или выполнение замены строки.

>>> import re
>>> p = re.compile(‘ab*’)
>>> print p
<_sre.SRE_Pattern object at 0x…>

re.compile()


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

>>> p = re.compile('ab*', re.IGNORECASE)

Регулярное выражение передается re.compile() как строка. Регулярные выражения обрабатываются как строки, поскольку не являются частью языка Python, и нет никакого специального синтаксиса для их выражения. (Существуют приложения, которые вовсе не нуждаются в регулярных выражениях, так что нет необходимости забивать спецификацию языка, включая их.) Вместо этого имеется модуль re, представляющий собой обертку модуля на С, подобно модулям socket или zlib.

Передача регулярных выражений в виде строки позволяет Python быть проще, но имеет один недостаток, который является темой следующего раздела.

Бэкслеш бедствие
(Или обратная косая чума 🙂 )

Как было отмечено ранее, в регулярных выражениях для того, чтобы обозначить специальную форму или позволить символам потерять их особую роль, используется символ бэкслеша (''). Это приводит к конфликту с использованием в строковых литералах Python такого же символа с той же целью.

Скажем, вы хотите написать регулярное выражение, соответствующее section, которое надо найти в LaTeX-файле. Чтобы выяснить, что написать в коде программы, начнем со строки, которую необходимо сопоставить. Далее, вы должны избежать любых бэкслешей и других метасимволов, экранировав их обратной косой чертой, в результате чего в строке появляется часть \. Тогда, результирующая строка, которая должна быть передана re.compile () должен быть \section. Однако, для того, чтобы выразить это как строковый литерал Python, оба бэкслеша должны быть экранированы снова, то есть "\\section".

Одним словом, чтобы сопоставить бэкслеш, нужно писать в качестве строки регулярного выражения '\\', потому что регулярное выражение должно быть \, и каждая обратная косая черта должна быть переведена в обычную строку как \.

Решение заключается в использовании для регулярных выражений «сырых» строк (raw string); в строковых литералах с префиксом 'r' слэши никак не обрабатываются, так что r"n" это строка из двух символов (» и ‘n’), а "n" — из одного символа новой строки. Поэтому регулярные выражения часто будут записываться с использованием сырых строк.

Regular String Raw string
‘ab*’ r’ab*’
‘\\section’ r’\section*’
‘\w+\s+\1’ r’w+s+1′

Выполнение сопоставлений

После того, как у вас есть объект, представляющий скомпилированное регулярное выражение, что вы с ним будете делать? Объекты шаблонов имеют несколько методов и атрибутов. Только самые важные из них будут рассмотрены здесь; для получения полного списка ознакомьтесь с документацией re.

Метод/атрибут Цель
match() Определить, начинается ли совпадение регулярного выражения с начала строки
search() Сканировать всю строку в поисках всех мест совпадений с регулярным выражением
findall() Найти все подстроки совпадений с регулярным выражением и вернуть их в виде списка
finditer() Найти все подстроки совпадений с регулярным выражением и вернуть их в виде итератора

Если не было найдено ни одного совпадения, то match() и search() возвращают None. Если поиск успешен, возвращается экземпляр MatchObject, содержащий информацию о совпадении: где оно начинается и заканчивается, подстрока соответствия, и так далее.

Вы можете узнать об этом, интерактивно поэкспериментировав с модулем re. Вы также можете взглянуть на Tools/scripts/redemo.py, демонстрационную программу, включенную в дистрибутив Python. Она позволяет вводить регулярные выражения и строки, и отображает, есть ли совпадение с регулярным выражением или нет. redemo.py может быть весьма полезна для отладки сложных регулярных выражений. Kodos Фила Шварца — еще один интерактивный инструмент для разработки и тестирования моделей РВ.

В этом пособии мы используем для примеров стандартный интерпретатор Python:

>>> import re
>>> p = re.compile(‘[a-z]+’)
>>> p
<_sre.SRE_Pattern object at 0x…>

Теперь вы можете попробовать сравнить строки для регулярного выражения [a-z]+. Пустая строка ему не будет соответствовать, потому что + означает повторение «один или больше» раз. match() в этом случае должен вернуть None, что и видим:

>>> p.match("")
>>> print p.match("")
None

Теперь попробуем строку, которая должна совпасть с шаблоном: 'tempo'. В этом случае match() вернет MatchObject, который вы можете разместить в какой-то переменной, чтобы использовать ее в дальнейшем:

>>> m = p.match(‘tempo’)
>>> print m
<_sre.SRE_Match object at 0x…>

Теперь вы можете вызывать MatchObject для получения информации о соответствующих строках. Для MatchObject также имеется несколько методов и атрибутов, наиболее важными из которых являются:

Метод/атрибут Цель
group() Вернуть строку, сошедшуюся с регулярным выражением
start() Вернуть позицию начала совпадения
end() Вернуть позицию конца совпадения
span() Вернуть кортеж (start, end) позиций совпадения

>>> m.group()
‘tempo’
>>> m.start(), m.end()
(05)
>>> m.span()
(05)

Так как метод match() проверяет совпадения только с начала строки, start() всегда будет возвращать 0. Однако метод search() сканирует всю строку, так что для него начало не обязательно в нуле:

>>> print p.match(‘::: message’)
None
>>> m = p.search(‘::: message’) ; print m
<_sre.SRE_Match object at 0x…>
>>> m.group()
‘message’
>>> m.span()
(411)

В реальных программах наиболее распространенный стиль это хранение MatchObject в переменной, а затем проверка по None. Обычно это выглядит следующим образом:

p = re.compile( … )
m = p.match( ‘string goes here’ )
if m:
    print ‘Match found: ‘, m.group()
else:
    print ‘No match’

Два метода возвращают все совпадения для шаблона. findall() возвращает список совпавших подстрок:

>>> p = re.compile(d+’)
>>> p.findall(’12 drummers drumming, 11 pipers piping, 10 lords a-leaping’)
[’12’’11’’10’]

Метод findall() должен создать полный список, прежде чем он может быть возвращен в качестве результата. Метод finditer() возвращает последовательность экземпляров MatchObject в качестве итератора.

>>> iterator = p.finditer(’12 drummers drumming, 11 … 10 …’)
>>> iterator
<callable-iterator object at 0x401833ac>
>>> for match in iterator:
…     print match.span()

(02)
(2224)
(2931)

Функции на уровне модуля

Вам не обязательно нужно создавать объекты шаблонов и вызывать их методы; модуль re также предоставляет функции верхнего уровня match(), search(), findall(), sub() и так далее. Эти функции принимают те же аргументы, что и для шаблонов, со строкой РВ в качестве первого аргумента и также возвращают None или MatchObject.

>>> print re.match(r‘Froms+’‘Fromage amk’)
None
>>> re.match(r‘Froms+’‘From amk Thu May 14 19:12:10 1998’)
<_sre.SRE_Match object at 0x…>

Эти функции просто создают для вас объект шаблона и вызывают соответствующий метод. Они также хранят объект в кэше, так что будущие вызовы с использованием того же регулярного выражения будут быстрее.

Должны вы использовать эти функции или шаблоны с методами? Это зависит от того, как часто будет использоваться регулярное выражение и от вашего личного стиля кодинга. Если регулярное выражение используется только в одном месте кода, то такие функции, вероятно, более удобны. Если программа содержит много регулярных выражений, или повторно использует одни и те же в нескольких местах, то будет целесообразно собрать все определения в одном месте, в разделе кода, который предварительно компилирует все регулярные выражения. В качестве примера из стандартной библиотеки, вот кусок из xmllib.py:

ref = re.compile( ... )
entityref = re.compile( ... )
charref = re.compile( ... )
starttagopen = re.compile( ... )

Сам я предпочитаю работать со скомпилированными объектами, даже для одноразового использования, но мало кто окажется таким же пуристом в этом, как я.

Флаги компиляции

Флаги компиляции позволяют изменять некоторые аспекты того, как работают регулярные выражения. Флаги доступны в модуле под двумя именами: длинным, таким как IGNORECASE и коротким, в однобуквенной форме, таким как I. Несколько флагов могут быть заданы в форме двоичного ИЛИ; например re.I | re.M устанавливает флаги I и M.

DOTALL, S
Сопоставление, такое же как '.', то есть с любым символом, но при включении этого флага, в рассмотрение добавляется и символ новой строки.

IGNORECASE, I
Сопоставление без учета регистра; Например, [A-Z] будет также соответствовать и строчным буквам, так что Spam будет соответствовать Spam, spam, spAM и так далее.

LOCALE, L
Делает w, W, b, B зависящими от локализации. Например, если вы работаете с текстом на французском, и хотите написать w+ для того, чтобы находить слова, но w ищет только символы из множества [A-Za-z] и не будет искать ‘é’ или ‘ç’. Если система настроена правильно и выбран французский язык, ‘é’ также будет рассматриваться как буква.

MULTILINE, M
(Метасимволы ^ и $ еще не были описаны; они будут представлены немного позже, в начале второй части этого пособия.

Обычно ^ ищет соответствие только в начале строки, а $ только в конце непосредственно перед символом новой строки (если таковые имеются). Если этот флаг указан, ^ сравнение происходит во всех строках, то есть и в начале, и сразу же после каждого символа новой строки. Аналогично для $.

UNICODE, U
Делает w, W, b, B, d, D, s, S соответствующими таблице Unicode.

VERBOSE, X
Включает многословные (подробные) регулярные выражения, которые могут быть организованы более ясно и понятно. Если указан этот флаг, пробелы в строке регулярного выражения игнорируется, кроме случаев, когда они имеются в классе символов или им предшествует неэкранированный бэкслеш; это позволяет вам организовать регулярные выражения более ясным образом. Этот флаг также позволяет помещать в регулярные выражения комментарии, начинающиеся с '#', которые будут игнорироваться движком.

Пример того, как РВ становится существенно проще читать:

charref = re.compile(r"""
 &[#]                     # Start of a numeric entity reference
 (
     0[0-7]+            # Octal form
   | [0-9]+              # Decimal form
   | x[0-9a-fA-F]+   # Hexadecimal form
 )
 ;                           # Trailing semicolon
"""
re.VERBOSE)

Без verbose это выглядело бы так:

charref = re.compile("&#(0[0-7]+"
                     "|[0-9]+"
                     "|x[0-9a-fA-F]+);")

В приведенном выше примере была использована автоматическая конкатенация Python строковых литералов для разбивки РВ на более мелкие части, но, все равно, без пояснений это пример труднее понять, чем версию с помощью re.VERBOSE.

На этом месте мы пока завершим наше рассмотрение. Советую немного отдохнуть перед второй половиной, содержащей рассказ о других метасимволах, методах разбиения, поиска и замены строк и большое количество примеров использования регулярных выражений.

Продолжение

habr.com

Общая информация

Регулярное выражение (regular expression, regexp, регэксп) — механизм, позволяющий задать шаблон для строки и осуществить поиск данных, соответствующих этому шаблону в заданном тексте. Кроме того, дополнительные функции по работе с regexp’ами позволяют получить найденные данные в виде массива строк, произвести замену в тексте по шаблону, разбиение строки по шаблону и т.п. Однако главной их функцией, на которой основаны все остальные, является именно функция поиска в тексте данных, соответствующих шаблону, описанному в синтаксисе регулярных выражений.

Очень часто регулярные выражения используются для того, чтобы проверить, является ли данная строка строкой в необходимом формате. Например следующий regexp предназначен для проверки того, что строка содержит корректный e-mail адрес:

/^w+([.w]+)*w@w((.w)*w+)*.w{2,3}$/

Выглядит, на первый взгляд, довольно страшно 🙂 Но, тем не менее, это работает, и работает очень хорошо. А когда вы научитесь писать и использовать regexp’ы в своем коде — это еще будет и сильно облегчать вам жизнь.

Регулярные выражения пришли к нам из Unix и Perl. В PHP существует два различных механизма для обработки регулярных выражений: POSIX-совместимые и Perl-совместимые. Их синтаксис во многом похож, однако Perl-совместимые регулярные выражения более мощные и, к тому же, работают намного быстрее (в некоторых случаях до 10 раз быстрее). Поэтому здесь мы будем вести речь только о Perl-совместимых регулярных выражениях. Кстати, необходимо заметить, что полное описание синтаксиса регулярных выражений, имеющееся в PHP Manual, занимает более 50 килобайт и, естественно, здесь мы не будем рассматривать весь синтаксис. Нам необходимы только основы, которые помогут вам понять, как именно пишутся регулярные выражения.

Сутью механизма регулярных выражений является то, что они позволяют задать шаблон для нечеткого поиска по тексту. Например, если перед вами стоит задача найти в тексте определенное слово, то с этой задачай хорошо справляются и обычные функции работы со строками. Однако если вам нужно найти «то, не знаю что», о чем вы можете сказать только то, как приблизительно это должно выглядеть — то здесь без регулярных выражений просто не обойтись. Например, вам необходимо найти в тексте информацию, про которую вам известно только то, что это «3 или 4 цифры после которых через пробел идет 5 заглавных латинских букв«, то вы сможете сделать это очень просто, возпользовавшись следующим регулярным выражением:

/d{3,4}s[A-Z]{5}/

Синтаксис регулярных выражений

Регулярные выражения, как уже было сказано выше, представляют собой строку. Строка всегда начинается с символа разделителя, за которым следует непосредственно регулярное выражение, затем еще один символ разделителя и потом необязятельный список модификаторов. В качестве символа разделителя обычно используется слэш (‘/‘). Таким образом в следующем регулярном выражении: /d{3}-d{2}/m, символ ‘/‘ является разделителем, строка ‘d{3}-d{2}‘ — непосредственно регулярным выражением, а символ ‘m‘, расположенный после второго разделителя — это модификатор.

Основой синтаксиса регулярных выражений является тот факт, что некоторые символы, встречающиеся в строке рассматриваются не как обычные символы, а как имеющие специальное значение (т.н. метасимволы). Именно это решение позволяет работать всему механизму регулярных выражений. Каждый метасимвол имеет свою собственную роль в синтаксисе регулярных выражений. Далее мы рассмотрим все эти метасимволы.

Одним из самых важных метасимволов является символ обратного слэша (‘‘). Если в строке встречается этот символ, то парсер рассматривает символ, непосредственно следующий за ним двояко:

  • если следующий символ в обычном режиме имеет какое-либо специальное значение, то он теряет это свое специальное значение и рассматривается как обычный символ. Это совершенно необходимо для того, чтобы иметь возможность вставлять в строку специальные символы, как обычные. Например метасимвол ‘.‘, в обычном режиме означает «любой единичный символ«, а ‘.‘ означает просто точку. Также можно лишить специального значения и сам этот символ: ‘\‘.
  • если следующий символ в обычном режиме не имеет никакого специального значения, то он может получить такое значение, будучи соединенным с символом ‘‘. К примеру символ ‘d‘ в обычном режиме воспринимается просто как буква, однако, будучи соединенной с обратным слэшем (‘d‘) становится метасимволом, означающим «любая цифра«.

Существует множество символов, которые образуют метасимволы в паре с обратным слэшем. Как правило подобные пары используются для того, чтобы показать, что на этом месте в строке должен находиться символ, с кодом, который не имеет соответствующего ему изображения или же символ, принадлежащий какой-то определенной группе символов. Ниже приведены некоторые наиболее употребительные:

Метасимвол Значение
Метасимволы для задания символов, не имеющих изображения
n Символ перевода строки (код 0x0A)
r Символ возврата каретки (код 0x0D)
t Символ табуляции (код 0x09)
xhh Вставка символа с шестнадцатиричным кодом 0xhh, например x41 вставит латинскую букву ‘A
Метасимволы для задания групп символов
d Цифра (0-9)
D Не цифра (любой символ кроме символов 0-9)
s Пустой символ (обычно пробел и символ табуляции)
S Непустой символ (все, кроме символов, определяемых метасимволом s)
w «Словесный» символ (символ, который используется в словах. Обычно все буквы, все цифры и знак подчеркивания (‘_‘))
W Все, кроме символов, определяемых метасимволом w

С остальными имеющимися метасимволами, построенными по этому принципу вы при желании сможете ознакомиться в оригинальном описании.

Приведу несколько простейших примеров для того, чтобы вы понимали, о чем идет речь. Сразу оговорюсь, что примеры несколько громоздки и некрасивы, но лишь потому, что я не стал использовать в них метасимволы, о которых еще не рассказал и которые сделали бы их намного проще.

Regexp Комментарии
/ddd/ Любое трехзначное число (‘123‘, ‘719‘, ‘001‘)
/wsdd/ Буква, пробел (или табуляция) и двузначное число (‘A 01‘, ‘z 45‘, ‘S 18‘)
/d and d/ Любая из следующих строк: ‘1 and 2‘, ‘9 and 5‘, ‘3 and 4‘.

Синтаксис регулярных выражений имеет средства для определения собственных подмножеств символов. Например вам может понадобиться задать условие, что в этом месте строки должна находиться шестнадцатиричная цифра или еще что-то подобное. Для описания таких подмножеств применяются символы квадратных скобок ‘[]‘. Квадратные скобки, встреченные внутри регулярного выражения считаются одним символом, который может принимать значения, перечисленные внутри этих скобок.

Есть небольшая тонкость в том, как работают метасимволы внутри квадратных скобок. Дело в том, что в синтаксисе регулярных выражений существует еще множество метасимволов, но практически все они работают только вне секций описаний подмножеств. Единственные метасимволы, которые работают внутри этих секций это:

  • Обратный слэш (‘‘). Т.е. все метасимволы из приведенной ранее таблицы будут работать.
  • Минус (‘-‘). Используется для задания набора символов из одного промежутка (например все цифры могут быть заданы как ‘0-9‘)
  • Символ ‘^‘. Если этот символ стоит первым в секции задания подмножества символов (и только в этом случае!) он будет рассматриваться как символ отрицания. Т.о. можно задать все сиволы, которые не описаны в данной секции.

Несколько примеров, чтобы было понятно, как это работает:

Regexp Комментарии
[0-9A-Fa-f] Цифра в шестнадцатиричной системе счисления
[dA-Fa-f] То же самое, но с использованием метасимвола
[02468] Четная цифра
[^d] Все, кроме цифр (аналог метасимвола D)
[a^b] Любой из символов ‘a‘, ‘b‘, ‘^‘. Заметьте, что здесь символ ‘^‘ не имеет какого-либо специального значения, потому что стоит не на первой позиции внутри квадратных скобок.

Теперь необходимо рассмотреть еще несколько метасимволов. Как уже было сказано ранее, все они работают только вне секций описчаний подмножеств символов (вне квадратных скобок).

Символы ‘^‘ и ‘$‘. Они использутся для того, что того, чтобы указать парсеру регулярных выражений на то, чтобы он обратил внимание на положение искомого текста в строке. Символ ‘^‘ указывает, что искомый текст должен находиться в начале строки, символ ‘$‘ наоборот, указывает, что искомый текст должен находиться в конце строки. Посмотрим, как это работает на примере:

Допустим, у нас есть текст:

12 aaa bbb  aaa 27 ccc  aaa aaa 45    

И регулярное выражение для поиска чисел в этом тексте: /dd/m (не обращайте пока внимания на модификатор). Поиск по этому регулярному выражению вернет нам 3 значения: ‘12‘, ‘27‘, ‘45‘. Теперь ограничим поиск, указав, где именно внутри строки должен располагаться текст: /^dd/m. Здесь результат будет только один — ‘12‘, потому что только это число располагается в начале строки. Аналогично, регулярное выражение /dd$/m вернет результат ‘45‘.

Символ точки ‘.‘. Этот метасимвол указывает, что на данном месте в строке может находиться любой символ (за исключением символа перевода строки). Очень удобно использовать его, если вам нужно «пропустить» какую-нибудь букву в слове при проверке. Например регулярное выражение /.bc/ найдет в тексте и ‘abc‘ и ‘Abc‘ и ‘Zbc‘ и ‘5bc‘.

Символ вертикальной черты ‘ ‘. Используется для задания списка альтернатив. Например регулярное выражение:

/(красное зеленое) яблоко/

Найдет в тексте все словосочетания ‘красное яблоко‘ и ‘зеленое яблоко‘. О значении круглых скобок в этом выражении см. далее.

Символы круглых скобок ‘(‘ и ‘)‘. Эти символы позволяют получить из искомой строки дополнительную информацию. Обычно, если парсер регулярных выражений ищет в тексте информацию по заданному выражению и находит ее — он просто возвращает найденную строку. Однако, если он встречает внутри регулярного выражения круглые скобки, то он рассматривает содержимое этих скобок как еще одно регулярное выражение, по которому необходимо произвести поиск. Парсер рекурсивно вызывает сам себя для поиска по новому регулярному выражению и использует результаты поиска для дальнейшей обработки основного регулярного выражения. При этом, если поиск хотя бы по одному из внутренних регулярных выражений не увенчался успехом — поиск по всему регулярному выражению считается безуспешным.

Чтобы было понятнее, о чем я только что рассказал — рассмотрим в качестве примера то, как работает парсер регулярных выражений в случае приведенного выше регулярного выражения о яблоках: /(красное зеленое) яблоко/.

  1. Парсер начинает разбор регулярного выражения и встречает выражение в скобках: (красное зеленое)
  2. Парсер вызывает себя для поиска по найденному регулярному выражению
  3. Получив результаты поиска парсер подставляет по очереди каждый из полученных результатов на место выражения в скобках и смотрит, удовлетворяет ли найденный результат всем условиям основного регулярного выражения (в данном случае смотрит, есть ли после найденного слова слово «яблоко«).
  4. Если все в порядке — результаты поиска по каждому из имеющихся регулярных выражений для этого случая возвращаются, если нет — парсер просто переходит к следующему найденному фрагменту. Результат поиска внутреннего регулярного выражения для этого фрагмента при этом теряется.

В качестве примера возьмем строку:

яблоко красное и зеленое яблоко и еще одно красное яблоко и еще одно яблоко, зеленое

Поиск по внутреннему регулярному выражению даст 4 результата (выделены жирным шрифтом):

яблоко красное и зеленое яблоко и еще одно красное яблоко и еще одно яблоко, зеленое

Однако поиск по всему регулярному выражению даст всего 2 результата, потому как в остальных случаях условия основного регулярного выражения не выполняются:

яблоко красное и зеленое яблоко и еще одно красное яблоко и еще одно яблоко, зеленое

Необходимо заметить, что для этих двух случаев будет возвращен не только результат поиска по основному регулярному выражению, но и результат поиска по внутреннему регулярному выражению для каждого из найденных фрагментов. В большинстве случаев это полезно (пример — чуть позднее), но иногда наоборот, лучше избавиться от лишних результатов. В этом случае необходимо добавить символы ‘?:‘ непосредственно после открывающейся круглой скобки: /(?:красное зеленое) яблоко/.

Теперь пример, когда получение результатов внутренних регулярных выражений может быть полезным. Допустим, нам необходимо проверить, является ли строка семизначным телефонным номером с указанием кода города и получить из нее код города и номер телефона:

/((d{3,5}))s+(d{3}-d{2}-d{2})/

Некоторые из примененных здесь метасимволов вам еще неизвестны и будут рассмотрены чуть позднее. Давайте рассотрим этот regexp подробнее.

Первая круглая скобка здесь теряет свое специальное значение и будет рассматриваться как обычный символ:

/((d{3,5}))s+(d{3}-d{2}-d{2})/

Далее идет регулярное выражение в скобках (проверка кода города):

/((d{3,5}))s+(d{3}-d{2}-d{2})/

После этого идет закрывающая круглая скобка, которая также лишена своего специального значения из-за символа обратного слэша, стоящего перед ней:

/((d{3,5}))s+(d{3}-d{2}-d{2})/

Затем идет пропуск пустого места:

/((d{3,5}))s+(d{3}-d{2}-d{2})/

И еще одно регулярное выражение в скобках, которое проверяет номер телефона:

/((d{3,5}))s+(d{3}-d{2}-d{2})/

Как видите, здесь есть 3 регулярных выражения — основное и два внутренних. При этом основное выражение позволяет нам проверить, имеет ли строка необходимый нам формат, а два внутренних — получить соответственно код города и номер телефона. Т.е. одним регулярным выражением мы можем решить сразу несколько задач!

Посмотрим, как работает это регулярное выражение. Пусть у нас есть строка: «My phone is (095) 123-45-67«. Результатами поиска будут 3 строки: ‘(095) 123-45-67‘, ‘095‘ и ‘123-45-67‘.

Нам осталось рассмотреть еще одну группу метасимволов, определяющих количественные показатели (т.н. quantifiers). Как вы уже могли заметить ранее — очень часто бывает необходимо указать, что какой-то символ должен повторяться определенное количество раз. Конечно, можно просто указать его необходимое количество раз непосредственно в строке, но это, естественно не выход. Тем более, что очень часто встречаются ситуации, когда точное количество символов неизвестно. Поэтому синтаксис регулярных выражений содержит набор метасимволов, предназначенных именно для решения подобных задач. Каждый из описанных ниже метасимволов определяет количественную характеристику символа который находится непосредственно перед ним.

Звездочка ‘*‘. Указывает, что символ должен быть повторен 0 или более раз (т.е. символ может отсутствовать или присутствовать в любых количествах). Пример: выражение /ab*c/ найдет строки ‘ac‘, ‘abc‘, ‘abbc‘ и т.д.

Плюс ‘+‘. Указывает, что символ должен быть повторен 1 или более раз (т.е. символ обязан присутствовать и может присутствовать в любых количествах). Пример: выражение /ab+c/ найдет строки ‘abc‘, ‘abbc‘, ‘abbbc‘ и т.д., но не найдет строку ‘ac‘.

Знак вопроса ‘?‘. Указывает, что символ моет как присутствовать, так и нет, но при этом не может повторяться более одного раза. Пример: выражение /ab?c/ найдет строки ‘ac‘ и ‘abc‘, но не найдет строку ‘abbc‘.

Фигурные скобки ‘{‘ и ‘}‘. Определяют количественную характеристику символа. Внутри скобок через запятую перечисляются минимальное и максимальное количество повторений символа. При этом любой из параметров может быть опущен, а кроме того можно задать точное количество повторений, указав только одно число. Примеры:

  • {2,4} — символ долен повториться минимум 2 раза, но не более 4.
  • {,5} — символ может отсутствовать (т.к. не задано минимальное количество повторений), но если присутствует, то не должен повторяться более 5 раз.
  • {3,} — символ должен повторяться минимум 3 раза, но может быть и больше.
  • {4} — символ должен повторяться ровно 4 раза

Есть еще одна тонкость в использовании метасимвола ‘?‘. Посмотрите на такое выражение: /.+a/. Ожидается, что оно вернет нам часть текста до первого вхождения символа ‘a‘ в этот текст. На самом деле оно будет работать несколько не так, как ожидается и результатом поиска будет весь текст до последнего вхождения символа ‘a‘. Дело в том, что по умолчанию количественные метасимволы «жадничают» и пытаются захватить как можно больший кусок текста. Если это не нужно (как а нашем случае), то необходимо «отучить» их от жадности, указав знак ‘?‘ после количественного метасимвола: /.+?a/. После этого выражение будет работать так как надо.

Модификаторы регулярных выражений

Как уже было сказано ранее — механизм регулярных выражений позволяет добавлять модификаторы, влияющие на обработку регулярного выражения. Ниже рассмотрены наиболее употребительные, об остальных вы можете прочитать в оригинальном описании.

Модификатор Значение
i Включение режима case-insensitive, т.е. большие и маленькие буквы в выражении не различаются.
m Указывает на то, что текст, по которому ведется поиск, должен рассматриваться как состоящий из нескольких строк. По умолчанию механизм регулярных выражений рассматривает текст как одну строку вне зависимости от того, чем она является на самом деле. Соответственно метасимволы ‘^‘ и ‘$‘ указывают на начало и конец всего текста. Если же этот модификатор указан, то они будут указывать соответственно на начало и конец каждой строки текста.
s По умолчанию метасимвол ‘.‘ не включает в свое определение символ перевода строки. Т.е. для многострочного текста выражение /.+/ вернет только первую строку, а не весь текст, как ожидается. Указание этого модификатора снимает это ограничение.
U Делает все количественные метасимволы «не жадными» по умолчанию (про «жадность» количественных метасимволов см. выше)

Заключение

На этом мы заканчиваем рассмотрение синтаксиса регулярных выражений, но тема еще не закончена. В следующем выпуске мы рассмотрим вопросы того, как PHP работает с регулярными выражениями, а также рассмотрим некоторые практические примеры использования регулярных выражений в ваших программах.

Комментарий:
 

www.codenet.ru

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

.{5,10} — данному регулярному выражению соответствует последовательность от 5 до 10 любых символов, кроме переноса строки. Его можно использовать для обрезки текста до нужной длины, парсинга подстрок определенной длины, а так же для указания нужного количества неизвестного набора символов внутри текста.

аd+а — в случаях, когда между искомым текстом, который известен, есть числа, состоящие из разного количества знаков. Данному регулярному выражению соответствует а1а, а23а, а459а и так далее.

а[2-5]{2,4}а — данное регулярное выражение возьмет текст, который будет начинаться на “а” и содержать внутри от 2 до 4 цифр 2, 3, 4 или 5 и будет заканчиваться на “а”. К примеру, а354а или а52а

К примеру, вам необходимо взять url сайта из следующей строки id="123" <a href="www.site.com">, где номер id всегда меняется, а просто в теге a href находятся и другие урлы, которые не нужны.

Тестер регулярных выражений выдаст нам такой вариант:

но ему будут соответствовать только строки, айди которых равен «123». Т.к. нам нужно указать, что вместо 123 может быть любая последовательность цифр, мы заменяем их на d+ и получаем следующее регулярное выражение

которое получит все строки, содержащие любые айди.

Что касается самого короткого совпадения в регулярном выражении. Нужно понимать, что под самым коротким совпадением понимается часть текста, которая начинается и заканчивается по условию, заданному в регулярном выражении.

Если мы возьмём строку <a href="www.site.com"> <a href="www.site2.ru"> и применим к ней регулярное выражение “(?<=<a href=").*?.ru(?=">)”, в ответе мы получим www.site.com"> <a href="www.site2.ru вместо ожидаемого «www.site2.ru»

Почему поиск выдал нам не самое короткое совпадение? Происходит это потому, что регулярное выражение получило из текста первую часть, которая должна идти перед искомым текстом, т.е. “<a href="”. Далее продолжило искать до тех пор, пока не нашло условие, по которому должна закончиться строка, т.е. “.ru”, после которого идёт ">

Для того, чтобы этого избежать, в данном случае можно использовать такую структуру регулярного выражения:

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

В случае же, если в части регулярного выражения может быть всё, что угодно — различной длины различный текст, или же он может вообще отсутствовать, можно в данном месте вставить последовательность «.*?»

Начало и конец строки

Для указания того, что искомое значение начинается с новой строки и (или) заканчивается в конце строки, подойдёт такое регулярное выражение:

Часто такое обозначение помогает построить правильное регулярное выражение для поиска элементов на вебстранице.

В случаях, если мы ищем элемент на разных сайтах, и он может отображаться с разным регистром, к примеру: Строка, СТРОКА, строка. С точки зрения машины эти три значения будут различными, и найдется только то, которое прописано с учетом регистра.

Если нужно подготовить регулярное выражение, которое найдет все эти совпадения, необходимо прописать

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

Если необходимо взять текст с вебстраницы, почты или файла, то нужно учитывать, что начало строки, обозначаемое, как ^ — это только начало первой строки текста, а конец строки, обозначаемое как $ — это только конец последней строки.

Остальные строки в тексте имеют переносы, т.е. все эти строки заканчиваются символом возврата каретки (r) а начинаются символом новой строки (n).

Для того, чтобы обозначить, что регулярное выражение должно начинаться и заканчиваться в пределах одной строки, мы можем прописать условие ИЛИ, которое обозначается как вертикальный слеш — |

Регулярное выражение выглядит так:

Так же можно дополнительно использовать модификатор многострочного поиска, который каждую строку считает, как новую:

Подробнее об операторе ИЛИ

В регулярном выражении можно указать, что именно искать, используя оператор ИЛИ следующим образом:

1|2 (что будет обозначать выбор 1 или 2).

Таким образом, мы можем проверять на странице наличие сразу нескольких текстов.

К примеру, Вам необходимо проверить на странице фразы "Привет", "Спасибо за регистрацию", "Приветствуем Вас", мы можем объединить все эти данные в одно регулярное выражение следующим образом:

При таком построении, проверка наличия текста на странице получит один из указанных вариантов.

В случае же, если знак “ИЛИ” необходимо использовать не для всего регулярного выражения, как показано выше, а только для его части, эта часть должна находиться внутри скобок. Например:

Данное регулярное выражение найдет фразы "Что Он делает." "Что Она делает." и "Что Они делают."

Еще несколько примеров готовых регулярных выражений:

<.*?> — найдет все теги в искомом тексте;

[.-_A-Za-z0-9][email protected][.-A-Za-z0-9]+?[.A-Za-z0-9]{2,} — найдет все email;

(d{1,3}.){3}d{1,3}:d{1,5} — получить айпи и порт;

(http|ftp|https)://[w-_]+(.[w-_]+)+([w-.,@?^=%&amp;:/~+#]*[w-@?^=%&amp;/~+#])? – получит урлы сайтов.

Всем спасибо, кто осилил данную статью, надеюсь она была полезна прочитавшим её пользователям и пригодится еще многим.

zennolab.com


You May Also Like

About the Author: admind

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.