класс Regexp
A Regexp содержит регулярное выражение, используемое для сопоставления шаблона со строками. Регулярные выражения создаются с помощью литералов /.../ и %r{...}, а также конструктора Regexp::new.
Регулярные выражения (regexp) представляют собой шаблоны, описывающие содержимое строки. Они используются для проверки, содержит ли строка заданный шаблон, или извлечения совпадающих фрагментов. Они создаются с помощью литералов /pat/ и %r{pat} или конструктора Regexp.new.
Регулярное выражение обычно ограничено с помощью обратных слэшей (/). Например:
/hay/ =~ 'haystack' #=> 0
/y/.match('haystack') #=> #<MatchData "y">
Если строка содержит шаблон, то говорят, что она совпадает. Литеральная строка совпадает сама с собой.
Здесь "стог_сена" не содержит шаблон "иголка", поэтому она не совпадает:
/needle/.match('haystack') #=> nil
Здесь "стог_сена" содержит шаблон "стог", поэтому она совпадает:
/hay/.match('haystack') #=> #<MatchData "hay">
В частности, /st/ требует, чтобы строка содержала букву s, за которой следует буква t, поэтому она также совпадает с стог_сена.
=~ и #match
Сопоставление шаблонов можно выполнить, используя оператор =~ или метод #match.
=~ оператор
=~ — базовый оператор сопоставления шаблонов в Ruby. Когда один операнд — регулярное выражение, а другой — строка, то регулярное выражение используется как шаблон для сопоставления со строкой. (Этот оператор эквивалентно определён Regexp и String, поэтому порядок String и Regexp не имеет значения. У других классов могут быть разные реализации оператора =~.) Если совпадение найдено, оператор возвращает индекс первого совпадения в строке, в противном случае возвращает nil.
/hay/ =~ 'haystack' #=> 0 'haystack' =~ /hay/ #=> 0 /a/ =~ 'haystack' #=> 1 /u/ =~ 'haystack' #=> nil
Используя оператор =~ со строкой и Regexp, переменная $~ устанавливается после успешного совпадения. $~ содержит объект MatchData. ::last_match эквивалентно $~.
#match метод
Метод match возвращает объект MatchData:
/st/.match('haystack') #=> #<MatchData "st">
Метасимволы и экранирование
Следующие являются метасимволами (, ), [, ], {, }, ., ?, +, *. Они имеют определённое значение при появлении в шаблоне. Чтобы сопоставить их буквально, они должны быть экранированы обратной косой чертой. Чтобы сопоставить обратную косую черту буквально, нужно экранировать её: \\\.
/1 \+ 2 = 3\?/.match('Does 1 + 2 = 3?') #=> #<MatchData "1 + 2 = 3?">
Шаблоны ведут себя как строки в двойных кавычках, поэтому могут содержать те же экранированные обратные косые черты.
/\s\u{6771 4eac 90fd}/.match("Go to 東京都")
#=> #<MatchData " 東京都">
Произвольные выражения Ruby могут быть вставлены в шаблоны с помощью конструкции #{...}.
place = "東京都"
/#{place}/.match("Go to 東京都")
#=> #<MatchData "東京都">
Классы символов
Класс символов ограничен квадратными скобками ([, ]) и перечисляет символы, которые могут появиться в этом месте совпадения. /[ab]/ означает a или b, в отличие от /ab/ , которое означает a, за которым следует b.
/W[aeiou]rd/.match("Word") #=> #<MatchData "Word">
Внутри класса символов дефис (-) является метасимволом, обозначающим интервал символов. [abcd] эквивалентно [a-d]. Диапазон может следовать за другим диапазоном, поэтому [abcdwxyz] эквивалентно [a-dw-z]. Порядок, в котором появляются диапазоны или отдельные символы внутри класса символов, не имеет значения.
/[0-9a-f]/.match('9f') #=> #<MatchData "9">
/[9f]/.match('9f') #=> #<MatchData "9">
Если первым символом класса символов является каретка (^), класс инвертируется: он сопоставляет любой символ кроме указанных.
/[^a-eg-z]/.match('f') #=> #<MatchData "f">
Класс символов может содержать другой класс символов. Само по себе это бесполезно, потому что [a-z[0-9]] описывает тот же набор, что и [a-z0-9]. Однако классы символов также поддерживают оператор &&, который выполняет пересечение множеств своих аргументов. Два можно объединить следующим образом:
/[a-w&&[^c-g]z]/ # ([a-w] AND ([^c-g] OR z))
Это эквивалентно:
/[abh-w]/
Следующие метасимволы также ведут себя как классы символов:
-
/./- Любой символ, кроме новой строки. -
/./m- Любой символ (модификаторmвключает многострочный режим) -
/\w/- Символ слова ([a-zA-Z0-9_]) -
/\W/- Несимвол слова ([^a-zA-Z0-9_]). Обратите внимание на Bug #4044, если используется/\W/с модификатором/i. -
/\d/- Символ цифры ([0-9]) -
/\D/- Несимвол цифры ([^0-9]) -
/\h/- Символ шестнадцатеричной цифры ([0-9a-fA-F]) -
/\H/- Несимвол шестнадцатеричной цифры ([^0-9a-fA-F]) -
/\s/- Символ пробела:/[ \t\r\n\f\v]/ -
/\S/- Несимвол пробела:/[^ \t\r\n\f\v]/
POSIX скобочные выражения также похожи на классы символов. Они предоставляют переносимую альтернативу вышеперечисленному, с дополнительным преимуществом, заключающимся в том, что они охватывают символы, отличные от ASCII. Например, /\d/ соответствует только десятичным цифрам ASCII (0-9); тогда как /[[:digit:]]/ соответствует любому символу в категории Unicode Nd.
-
/[[:alnum:]]/- Буквенно-цифровой символ -
/[[:alpha:]]/- Буквенный символ -
/[[:blank:]]/- Пробел или табуляция -
/[[:cntrl:]]/- Символ управления -
/[[:digit:]]/- Цифра -
/[[:graph:]]/- Непустой символ (исключает пробелы, управляющие символы и аналогичные) -
/[[:lower:]]/- Маленькая буквенная буква -
/[[:print:]]/- Как [:graph:], но включает символ пробела -
/[[:punct:]]/- Знак пунктуации -
/[[:space:]]/- Символ пробела ([:blank:], новая строка, возврат каретки и т. д.) -
/[[:upper:]]/- Заглавная буквенная буква -
/[[:xdigit:]]/- Цифра, допустимая в шестнадцатеричном числе (т. е. 0-9a-fA-F)
Ruby также поддерживает следующие не-POSIX классы символов:
-
/[[:word:]]/- Символ в одной из следующих категорий Unicode: Letter, Mark, Number, Connector_Punctuation -
/[[:ascii:]]/- Символ в наборе символов ASCII# U+06F2 is "EXTENDED ARABIC-INDIC DIGIT TWO" /[[:digit:]]/.match("\u06F2") #=> #<MatchData "\u{06F2}"> /[[:upper:]][[:lower:]]/.match("Hello") #=> #<MatchData "He"> /[[:xdigit:]][[:xdigit:]]/.match("A6") #=> #<MatchData "A6">
Повторение
Конструкции, описанные до сих пор, сопоставляют один символ. За ними может следовать метасимвол повторения, чтобы указать, сколько раз они должны повторяться. Такие метасимволы называются квантификаторами.
-
*- Ноль или более раз -
+- Один или более раз -
?- Ноль или один раз (необязательно) -
{n}- Ровно n раз -
{n,}- n или более раз -
{,m}- m или меньше раз -
{n,m}- Не менее n и не более m раз
По крайней мере один символ в верхнем регистре ('H'), по крайней мере один символ в нижнем регистре ('e'), две буквы 'l', затем одна 'o':
"Hello".match(/[[:upper:]]+[[:lower:]]+l{2}o/) #=> #<MatchData "Hello">
По умолчанию повторение жадное: сопоставляется как можно больше вхождений, при этом позволяя общему сопоставлению быть успешным. В противовес этому, ленивое сопоставление делает минимальное количество вхождений, необходимых для общего успеха. Жадный метасимвол можно сделать ленивым, добавив за ним ?.
Оба шаблона ниже соответствуют строке. Первый использует жадный квантификатор, поэтому '.+' соответствует '<a><b>'; второй использует ленивый квантификатор, поэтому '.+?' соответствует '<a>':
/<.+>/.match("<a><b>") #=> #<MatchData "<a><b>">
/<.+?>/.match("<a><b>") #=> #<MatchData "<a>">
Квантификатор, за которым следует + , сопоставляется поглощающе: после сопоставления он не возвращается. Они ведут себя как жадные квантификаторы, но, сопоставив, отказываются «отказаться» от своего сопоставления, даже если это ставит под угрозу общее сопоставление.
Захват
Скобки могут использоваться для захвата. Текст, заключённый в n<sup>той</sup> группе скобок, может быть впоследствии отнесён к n. Внутри шаблона используйте обратную ссылку \n; вне шаблона используйте MatchData[n].
‘at’ захватывается первой группой скобок, а затем ссылается на неё позже с \1:
/[csh](..) [csh]\1 in/.match("The cat sat in the hat")
#=> #<MatchData "cat sat in" 1:"at">
#match возвращает объект MatchData, который делает захваченный текст доступным с помощью его метода []:
/[csh](..) [csh]\1 in/.match("The cat sat in the hat")[1] #=> 'at'
К группам захвата можно обращаться по имени, если они определены с помощью конструкций (?<name>) или (?'name').
/\$(?<dollars>\d+)\.(?<cents>\d+)/.match("$3.67")
=> #<MatchData "$3.67" dollars:"3" cents:"67">
/\$(?<dollars>\d+)\.(?<cents>\d+)/.match("$3.67")[:dollars] #=> "3" Имена групп можно обработать с помощью \k<name>, где name — имя группы.
/(?<vowel>[aeiou]).\k<vowel>.\k<vowel>/.match('ototomy')
#=> #<MatchData "ototo" vowel:"o">
Примечание: Регулярное выражение не может одновременно использовать именованные обратные ссылки и пронумерованные обратные ссылки.
При использовании именованных групп захвата с литеральным регулярным выражением в левой части выражения и оператором =~, захваченный текст также присваивается локальным переменным с соответствующими именами.
/\$(?<dollars>\d+)\.(?<cents>\d+)/ =~ "$3.67" #=> 0 dollars #=> "3"
Группирование
Скобки также группируют термины, которые они заключают в себе, позволяя им быть квантифицированы как одно атомарное целое.
Шаблон ниже соответствует гласной, за которой следуют 2 символа слова:
/[aeiou]\w{2}/.match("Caenorhabditis elegans") #=> #<MatchData "aen">
В то время как следующий шаблон соответствует гласной, за которой дважды следует символ слова, т.е. [aeiou]\w[aeiou]\w: 'enor'.
/([aeiou]\w){2}/.match("Caenorhabditis elegans")
#=> #<MatchData "enor" 1:"or">
Конструкции (?:…) обеспечивают группирование без захвата. То есть, она объединяет содержащиеся в ней элементы в атомарное целое без создания обратной ссылки. Это повышает производительность в ущерб читабельности.
Первая группа скобок захватывает 'n', а вторая — 'ti'. Ко второй группе позже обращаются с помощью обратной ссылки \2:
/I(n)ves(ti)ga\2ons/.match("Investigations")
#=> #<MatchData "Investigations" 1:"n" 2:"ti">
Теперь первая группа скобок сделана незахватывающей с помощью '?:', поэтому она всё ещё соответствует 'n', но не создаёт обратную ссылку. Таким образом, обратная ссылка \1 теперь относится к 'ti'.
/I(?:n)ves(ti)ga\1ons/.match("Investigations")
#=> #<MatchData "Investigations" 1:"ti">
Атомарное группирование
Группирование может быть сделано атомарным с помощью (?>pat). Это заставляет подвыражение pat соответствовать независимо от остальной части выражения, таким образом, сопоставленное им значение становится фиксированным для остальной части соответствия, если только всё подвыражение не должно быть оставлено и впоследствии повторно проанализировано. Таким образом, pat обрабатывается как неделимая единица. Атомарное группирование обычно используется для оптимизации шаблонов, чтобы предотвратить излишнее возвращение к предыдущим шагам сопоставления в регулярном выражении.
" в шаблоне ниже соответствует первому символу строки, а затем .* соответствует Quote“. Это приводит к неудачному общему соответствию, поэтому текст, соответствующий .* возвращается на одну позицию назад, оставляя последний символ строки доступным для соответствия "
/".*"/.match('"Quote"') #=> #<MatchData "\"Quote\"">
Если .* сгруппировано атомарно, оно отказывается возвращаться к Quote“, даже если это означает неудачное общее соответствие.
/"(?>.*)"/.match('"Quote"') #=> nil
Вызовы подвыражений
Синтаксис \g<name> соответствует предыдущему подвыражению с именем name, которое может быть именем группы или номером, ещё раз. Это отличается от обратных ссылок тем, что оно повторно выполняет группу, а не просто пытается повторно сопоставить тот же текст.
Этот шаблон соответствует символу ( и присваивает его группе paren, пытается повторно вызвать подвыражение paren, но терпит неудачу, а затем соответствует литералу ):
/\A(?<paren>\(\g<paren>*\))*\z/ =~ '()' /\A(?<paren>\(\g<paren>*\))*\z/ =~ '(())' #=> 0 # ^1 # ^2 # ^3 # ^4 # ^5 # ^6 # ^7 # ^8 # ^9 # ^10
-
Соответствует началу строки, то есть перед первым символом.
-
Входит в именованную группу захвата, называемую
paren -
Сопоставляется с литералом (, первым символом в строке
-
Повторно вызывает группу
paren, т.е. рекурсивно возвращается ко второму шагу -
Повторно входит в группу
paren -
Сопоставляется с литералом (, вторым символом в строке
-
Попытаться вызвать
parenв третий раз, но потерпит неудачу, так как это помешает успешному общему соответствию -
Сопоставить литерал ), третий символ в строке. Помечает конец второго рекурсивного вызова
-
Сопоставить литерал ), четвёртый символ в строке
-
Сопоставить конец строки
Альтернация
Метасимвол вертикальной черты (|) объединяет два выражения в одно, которое соответствует любому из них. Каждое выражение является альтернативой.
/\w(and|or)\w/.match("Feliformia") #=> #<MatchData "form" 1:"or">
/\w(and|or)\w/.match("furandi") #=> #<MatchData "randi" 1:"and">
/\w(and|or)\w/.match("dissemblance") #=> nil
Свойства символов
Конструкции \p{} соответствуют символам с указанным свойством, подобно POSIX скобкам.
-
/\p{Alnum}/- Символ буквенно-цифровой -
/\p{Alpha}/- Символ буквы -
/\p{Blank}/- Пробел или табуляция -
/\p{Cntrl}/- Контрольный символ -
/\p{Digit}/- Цифра -
/\p{Graph}/- Символ, не являющийся пробелом (исключая пробелы, управляющие символы и аналогичные) -
/\p{Lower}/- Символ строчной буквы -
/\p{Print}/- Как\p{Graph}, но включает символ пробела -
/\p{Punct}/- Знак препинания -
/\p{Space}/- Символ пробела ([:blank:], перевод строки, возврат каретки и т. д.) -
/\p{Upper}/- Символ заглавной буквы -
/\p{XDigit}/- Цифра, допустимая в шестнадцатеричном числе (т. е., 0-9a-fA-F) -
/\p{Word}/- Член одной из следующих категорий Unicode: Letter, Mark, Number, Connector_Punctuation -
/\p{ASCII}/- Символ набора символов ASCII -
/\p{Any}/- Любой символ Unicode (включая неназначенные) -
/\p{Assigned}/- Назначенный символ
Значение Общей категории символа Unicode также можно сопоставить с \p{Ab}, где Ab — аббревиатура категории, как описано ниже:
И наконец, \p{} соответствует скрипту символа Unicode. Поддерживаются следующие скрипты: арабский, армянский, балийский, бенгальский, бопомофо, шрифт Брайля, бугинский, бухид, канадский аборигенный, карийский, чам, чероки, общий, коптский, клинописный, кипрский, кириллический, дезерет, деванагари, эфиопский, грузинский, глаголический, готический, греческий, гуджарати, гурумукхи, хань, хангыль, хануно, еврейский, хирагана, унаследованный, каннада, катакана, каях-ли, харошти, кхмерский, лаосский, латинский, лепча, лимбу, линейный B, ликийский, лидийский, малаялам, монгольский, бирманский, новый тай лю, нко, огам, ол чики, древний италик, древнеперсидский, ориа, османья, фагс-па, финикийский, режанг, рунический, саураштра, шавиан, сингальский, сунданесский, силоти нагри, сирийский, тагальский, тагбануа, тай ле, тамильский, телугу, таана, тайский, тибетский, тифинак, угаритский, вай и и.
Кодовое значение Unicode U+06E9 называется «АРАБСКИЙ МЕСТО САЙДА» и принадлежит к арабскому письму:
/\p{Arabic}/.match("\u06E9") #=> #<MatchData "\u06E9">
Все свойства символов могут быть инвертированы путём предварения их имени символом крышки (^).
Буква 'A' не входит в категорию Unicode Ll (буква; строчная), поэтому это соответствие успешно:
/\p{^Ll}/.match("A") #=> #<MatchData "A">
Якоря
Якоря — это метасимволы, которые соответствуют нулевой ширине позиций между символами, закрепляя соответствие к определённой позиции.
-
^- Совпадение с началом строки -
$- Совпадение с концом строки -
\A- Совпадение с началом строки. -
\Z- Совпадение с концом строки. Если строка заканчивается новой строкой, совпадение происходит перед новой строкой -
\z- Совпадение с концом строки -
\G- Совпадение с первой позицией совпадения:В методах, таких как
String#gsubиString#scan, оно изменяется при каждой итерации. Изначально оно совпадает с началом подлежащего, а в каждой последующей итерации — с тем местом, где закончилось последнее совпадение." a b c".gsub(/ /, '_') #=> "____a_b_c" " a b c".gsub(/\G /, '_') #=> "____a b c"
В методах, таких как
Regexp#matchиString#match, которые принимают (необязательный) смещение, оно совпадает с началом поиска."hello, world".match(/,/, 3) #=> #<MatchData ","> "hello, world".match(/\G,/, 3) #=> nil
-
\b- Совпадение с границами слов вне скобок; возврат (0x08) внутри скобок -
\B- Совпадение с границами неслов -
(?=pat)- Позитивное предпросмотр утверждение: гарантирует, что следующие символы соответствуют pat, но не включает эти символы в сопоставленный текст -
(?!pat)- Отрицательное предпросмотр утверждение: гарантирует, что следующие символы не соответствуют pat, но не включает эти символы в сопоставленный текст -
(?<=pat)- Позитивное просмотр назад утверждение: гарантирует, что предшествующие символы соответствуют pat, но не включает эти символы в сопоставленный текст -
(?<!pat)- Отрицательное просмотр назад утверждение: гарантирует, что предшествующие символы не соответствуют pat, но не включает эти символы в сопоставленный текст
Если шаблон не закреплен, он может начинаться в любой точке строки:
/real/.match("surrealist") #=> #<MatchData "real">
Привязка шаблона к началу строки вынуждает соответствие начинаться там. 'real' не встречается в начале строки, поэтому совпадение не происходит:
/\Areal/.match("surrealist") #=> nil
Совпадение ниже не происходит, потому что, хотя 'Demand' содержит 'and', шаблон не встречается на границе слова.
/\band/.match("Demand")
В то время как в следующем примере 'and' закреплен на границе неслова, поэтому вместо совпадения с первым 'and' он совпадает с четвертым символом 'demand':
/\Band.+/.match("Supply and demand curve") #=> #<MatchData "and curve">
Шаблон ниже использует позитивное предпросмотр и позитивный просмотр назад для соответствия тексту, появляющемуся в тегах, без включения тегов в совпадение:
/(?<=<b>)\w+(?=<\/b>)/.match("Fortune favours the <b>bold</b>")
#=> #<MatchData "bold">
Параметры
Конечный разделитель для regexp может быть последован одним или более одиночными параметрами, которые управляют тем, как шаблон может соответствовать.
-
/pat/i- Игнорировать регистр -
/pat/m- Рассматривать новую строку как символ, совпадающий с. -
/pat/x- Игнорировать пробелы и комментарии в шаблоне -
/pat/o- Выполнить интерполяцию#{}только один раз
i, m, и x также могут быть применены на уровне подвыражения с конструкцией (?on-off), которая включает параметры on и отключает параметры off для выражения, заключенного в скобки.
/a(?i:b)c/.match('aBc') #=> #<MatchData "aBc">
/a(?i:b)c/.match('abc') #=> #<MatchData "abc">
Параметры также могут использоваться с Regexp.new:
Regexp.new("abc", Regexp::IGNORECASE) #=> /abc/i
Regexp.new("abc", Regexp::MULTILINE) #=> /abc/m
Regexp.new("abc # Comment", Regexp::EXTENDED) #=> /abc # Comment/x
Regexp.new("abc", Regexp::IGNORECASE | Regexp::MULTILINE) #=> /abc/mi
Режим свободного форматирования и комментарии
Как упоминалось выше, параметр x включает режим свободного форматирования. Литеральные пробелы внутри шаблона игнорируются, а символ решётки (#) вводит комментарий до конца строки. Это позволяет упорядочить компоненты шаблона более удобочитаемым образом.
Выдуманный шаблон для соответствия числу с необязательными десятичными знаками:
float_pat = /\A
[[:digit:]]+ # 1 or more digits before the decimal point
(\. # Decimal point
[[:digit:]]+ # 1 or more digits after the decimal point
)? # The decimal point and following digits are optional
\Z/x
float_pat.match('3.14') #=> #<MatchData "3.14" 1:".14">
Существует ряд стратегий для соответствия пробелам:
-
Используйте шаблон, такой как
\sили\p{Space}. -
Используйте экранированные пробелы, например
\, т.е. пробел, предшествуемый обратной косой чертой. -
Используйте класс символов, например
[ ].
Комментарии могут быть включены в шаблон, не являющийся x с конструкцией (?#comment), где comment — произвольный текст, игнорируемый движком regexp.
Комментарии в литералах regexp не могут содержать неэкранированные символы-разделители.
Кодировка
Регулярные выражения предполагают использование кодировки исходного текста. Это можно переопределить с помощью одного из следующих модификаторов.
-
/pat/u- UTF-8 -
/pat/e- EUC-JP -
/pat/s- Windows-31J -
/pat/n- ASCII-8BIT
Regexp может быть сопоставлен со строкой, когда они либо используют одну кодировку, либо кодировка regexp равна US-ASCII, а кодировка строки — ASCII-совместима.
Если попытка сопоставления между несовместимыми кодировками, генерируется исключение Encoding::CompatibilityError.
Предикат Regexp#fixed_encoding? указывает, имеет ли regexp фиксированную кодировку, то есть несовместимую с ASCII. Кодировку regexp можно явно зафиксировать, передав Regexp::FIXEDENCODING в качестве второго аргумента Regexp.new:
r = Regexp.new("a".force_encoding("iso-8859-1"),Regexp::FIXEDENCODING)
r =~"a\u3042"
#=> Encoding::CompatibilityError: incompatible encoding regexp match
(ISO-8859-1 regexp with UTF-8 string) Специальные глобальные переменные
Сопоставление шаблонов устанавливает некоторые глобальные переменные:
-
$~эквивалентно ::last_match; -
$&содержит весь сопоставленный текст; -
$`содержит строку перед совпадением; -
$'содержит строку после совпадения; -
$1,$2и т.д. содержат текст, соответствующий первой, второй и т.д. группе захвата; -
$+содержит последнюю группу захвата.
Пример:
m = /s(\w{2}).*(c)/.match('haystack') #=> #<MatchData "stac" 1:"ta" 2:"c">
$~ #=> #<MatchData "stac" 1:"ta" 2:"c">
Regexp.last_match #=> #<MatchData "stac" 1:"ta" 2:"c">
$& #=> "stac"
# same as m[0]
$` #=> "hay"
# same as m.pre_match
$' #=> "k"
# same as m.post_match
$1 #=> "ta"
# same as m[1]
$2 #=> "c"
# same as m[2]
$3 #=> nil
# no third group in pattern
$+ #=> "c"
# same as m[-1]
Эти глобальные переменные являются локальными переменными потока и метода.
Производительность
Некоторые патологические сочетания конструкций могут привести к ужасно низкой производительности.
Рассмотрим строку из 25 a, d, 4 a и c.
s = 'a' * 25 + 'd' + 'a' * 4 + 'c' #=> "aaaaaaaaaaaaaaaaaaaaaaaaadaaaac"
Следующие шаблоны соответствуют мгновенно, как вы и ожидаете:
/(b|a)/ =~ s #=> 0 /(b|a+)/ =~ s #=> 0 /(b|a+)*/ =~ s #=> 0
Однако, следующий шаблон требует значительно больше времени:
/(b|a+)*c/ =~ s #=> 26
Это происходит потому, что атом в regexp квантифицируется как немедленным +, так и окружающим *, без чего-либо, что могло бы различать, какой из них управляет тем или иным символом. Возникающая неоднозначность приводит к сверхлинейной производительности. (Обратитесь к книге «Мастерство регулярных выражений» (3-е изд.), стр. 222, Джеффри Фридл, для углубленного анализа). Этот конкретный случай можно исправить, используя атомарные группировки, которые предотвращают ненужное возвращение назад:
(start = Time.now) && /(b|a+)*c/ =~ s && (Time.now - start) #=> 24.702736882 (start = Time.now) && /(?>b|a+)*c/ =~ s && (Time.now - start) #=> 0.000166571
Похожий случай демонстрирует следующий пример, который занимает примерно 60 секунд на выполнение для меня:
Сопоставить строку из 29 a с шаблоном из 29 необязательных a, за которыми следуют 29 обязательных a:
Regexp.new('a?' * 29 + 'a' * 29) =~ 'a' * 29
29 необязательных a соответствуют строке, но это не позволяет 29 обязательным a, которые следуют, соответствовать. Ruby должен многократно возвращаться назад, чтобы удовлетворить как можно больше необязательных соответствий, по-прежнему соответствовуя обязательным 29. Для нас очевидно, что ни одно из необязательных соответствий не может быть успешным, но Ruby, к сожалению, этого не понимает.
Лучший способ улучшить производительность — значительно уменьшить количество возвращений назад. В этом случае вместо отдельных соответствий 29 необязательных a, можно соответствовать диапазону необязательных a сразу с помощью a{0,29}:
Regexp.new('a{0,29}' + 'a' * 29) =~ 'a' * 29
Константы
Публичные методы класса
Псевдоним для Regexp.new
static VALUE
rb_reg_s_quote(VALUE c, VALUE str)
{
return rb_reg_quote(reg_operand(str, TRUE));
} Экранирует любые символы, которые имели бы специальное значение в регулярном выражении. Возвращает новую экранированную строку или self, если нет символов для экранирования. Для любой строки Regexp.new(Regexp.escape(str))=~str будет истинным.
Regexp.escape('\*?{}.') #=> \\\*\?\{\}\.
static VALUE
rb_reg_s_last_match(int argc, VALUE *argv)
{
VALUE nth;
if (argc > 0 && rb_scan_args(argc, argv, "01", &nth) == 1) {
VALUE match = rb_backref_get();
int n;
if (NIL_P(match)) return Qnil;
n = match_backref_number(match, nth);
return rb_reg_nth_match(n, match);
}
return match_getter();
} Первый вариант возвращает объект MatchData, сгенерированный последним успешным сопоставлением шаблона. Эквивалентно чтению специальной глобальной переменной $~ (см. Специальные глобальные переменные в Regexp для подробностей).
Второй вариант возвращает n-е поле в этом объекте MatchData. n может быть строкой или символом для ссылки на именованный захват.
Обратите внимание, что ::last_match локален для потока и области метода, который выполнил сопоставление шаблона.
/c(.)t/ =~ 'cat' #=> 0 Regexp.last_match #=> #<MatchData "cat" 1:"a"> Regexp.last_match(0) #=> "cat" Regexp.last_match(1) #=> "a" Regexp.last_match(2) #=> nil /(?<lhs>\w+)\s*=\s*(?<rhs>\w+)/ =~ "var = val" Regexp.last_match #=> #<MatchData "var = val" lhs:"var" rhs:"val"> Regexp.last_match(:lhs) #=> "var" Regexp.last_match(:rhs) #=> "val"
static VALUE
rb_reg_initialize_m(int argc, VALUE *argv, VALUE self)
{
int flags = 0;
VALUE str;
rb_encoding *enc = 0;
rb_check_arity(argc, 1, 3);
if (RB_TYPE_P(argv[0], T_REGEXP)) {
VALUE re = argv[0];
if (argc > 1) {
rb_warn("flags ignored");
}
rb_reg_check(re);
flags = rb_reg_options(re);
str = RREGEXP_SRC(re);
}
else {
if (argc >= 2) {
if (FIXNUM_P(argv[1])) flags = FIX2INT(argv[1]);
else if (RTEST(argv[1])) flags = ONIG_OPTION_IGNORECASE;
}
if (argc == 3 && !NIL_P(argv[2])) {
char *kcode = StringValuePtr(argv[2]);
if (kcode[0] == 'n' || kcode[0] == 'N') {
enc = rb_ascii8bit_encoding();
flags |= ARG_ENCODING_NONE;
}
else {
rb_warn("encoding option is ignored - %s", kcode);
}
}
str = StringValue(argv[0]);
}
if (enc && rb_enc_get(str) != enc)
rb_reg_init_str_enc(self, str, enc, flags);
else
rb_reg_init_str(self, str, flags);
return self;
} Создаёт новый регулярный выражения из pattern, который может быть либо строкой, либо объектом Regexp (в этом случае опции regexp будут распространены), и новые опции не могут быть указаны (изменение с версии Ruby 1.8).
Если options является целым числом, оно должно быть одним или несколькими из констант Regexp::EXTENDED, Regexp::IGNORECASE и Regexp::MULTILINE, объединённых оператором or. В противном случае, если options не nil или false, регулярное выражение будет регистронезависимым.
r1 = Regexp.new('^a-z+:\s+\w+') #=> /^a-z+:\s+\w+/
r2 = Regexp.new('cat', true) #=> /cat/i
r3 = Regexp.new(r2) #=> /cat/i
r4 = Regexp.new('dog', Regexp::EXTENDED | Regexp::IGNORECASE) #=> /dog/ix
static VALUE
rb_reg_s_quote(VALUE c, VALUE str)
{
return rb_reg_quote(reg_operand(str, TRUE));
} Экранирует любые символы, которые имели бы специальное значение в регулярном выражении. Возвращает новую экранированную строку или self, если нет символов для экранирования. Для любой строки, Regexp.new(Regexp.escape(str))=~str будет истинным.
Regexp.escape('\*?{}.') #=> \\\*\?\{\}\.
static VALUE
rb_reg_s_try_convert(VALUE dummy, VALUE re)
{
return rb_check_regexp_type(re);
} Попробуйте преобразовать obj в Regexp, используя метод to_regexp. Возвращает преобразованный regexp или nil, если obj не может быть преобразован по какой-либо причине.
Regexp.try_convert(/re/) #=> /re/
Regexp.try_convert("re") #=> nil
o = Object.new
Regexp.try_convert(o) #=> nil
def o.to_regexp() /foo/ end
Regexp.try_convert(o) #=> /foo/
static VALUE
rb_reg_s_union_m(VALUE self, VALUE args)
{
VALUE v;
if (RARRAY_LEN(args) == 1 &&
!NIL_P(v = rb_check_array_type(rb_ary_entry(args, 0)))) {
return rb_reg_s_union(self, v);
}
return rb_reg_s_union(self, args);
} Возвращает объект Regexp, который является объединением заданных patterns, т.е. будет соответствовать любой из его частей. Patterns могут быть объектами Regexp, в этом случае их опции будут сохранены, или строками. Если шаблоны не заданы, возвращает /(?!)/. Поведение не определено, если какой-либо заданный шаблон содержит захват.
Regexp.union #=> /(?!)/
Regexp.union("penzance") #=> /penzance/
Regexp.union("a+b*c") #=> /a\+b\*c/
Regexp.union("skiing", "sledding") #=> /skiing|sledding/
Regexp.union(["skiing", "sledding"]) #=> /skiing|sledding/
Regexp.union(/dogs/, /cats/i) #=> /(?-mix:dogs)|(?i-mx:cats)/
Примечание: аргументы для ::union будут пытаться преобразовать в регулярное выражение с помощью to_regexp.
Общедоступные методы экземпляров
static VALUE
rb_reg_equal(VALUE re1, VALUE re2)
{
if (re1 == re2) return Qtrue;
if (!RB_TYPE_P(re2, T_REGEXP)) return Qfalse;
rb_reg_check(re1); rb_reg_check(re2);
if (FL_TEST(re1, KCODE_FIXED) != FL_TEST(re2, KCODE_FIXED)) return Qfalse;
if (RREGEXP_PTR(re1)->options != RREGEXP_PTR(re2)->options) return Qfalse;
if (RREGEXP_SRC_LEN(re1) != RREGEXP_SRC_LEN(re2)) return Qfalse;
if (ENCODING_GET(re1) != ENCODING_GET(re2)) return Qfalse;
if (memcmp(RREGEXP_SRC_PTR(re1), RREGEXP_SRC_PTR(re2), RREGEXP_SRC_LEN(re1)) == 0) {
return Qtrue;
}
return Qfalse;
} Равенство—Два объекта регулярных выражений равны, если их шаблоны идентичны, у них одинаковый код набора символов, и их casefold? значения совпадают.
/abc/ == /abc/x #=> false /abc/ == /abc/i #=> false /abc/ == /abc/u #=> false /abc/u == /abc/n #=> false
VALUE
rb_reg_eqq(VALUE re, VALUE str)
{
long start;
str = reg_operand(str, FALSE);
if (NIL_P(str)) {
rb_backref_set(Qnil);
return Qfalse;
}
start = rb_reg_search(re, str, 0, 0);
if (start < 0) {
return Qfalse;
}
return Qtrue;
} Равенство с учётом регистра—Используется в операторах case.
a = "HELLO" case a when /^[a-z]*$/; print "Lower case\n" when /^[A-Z]*$/; print "Upper case\n" else; print "Mixed case\n" end #=> "Upper case"
Последовательность из литерала регулярного выражения и оператора === позволяет сравнивать с строкой.
/^[a-z]*$/ === "HELLO" #=> false /^[A-Z]*$/ === "HELLO" #=> true
VALUE
rb_reg_match(VALUE re, VALUE str)
{
long pos = reg_match_pos(re, &str, 0);
if (pos < 0) return Qnil;
pos = rb_str_sublen(str, pos);
return LONG2FIX(pos);
} Сопоставление—Сопоставляет rxp со строкой str.
/at/ =~ "input data" #=> 7 /ax/ =~ "input data" #=> nil
Если =~ используется с литералом регулярного выражения с именованными захватами, захваченные строки (или nil) присваиваются локальным переменным, именованным по именам захватов.
/(?<lhs>\w+)\s*=\s*(?<rhs>\w+)/ =~ " x = y " p lhs #=> "x" p rhs #=> "y"
Если сопоставление не найдено, переменным присваивается значение nil.
/(?<lhs>\w+)\s*=\s*(?<rhs>\w+)/ =~ " x = " p lhs #=> nil p rhs #=> nil
Это присвоение реализуется в парсере Ruby. Парсер обнаруживает 'regexp-литерал =~ выражение' для присвоения. Регулярное выражение должно быть литералом без интерполяции и расположено в левой части.
Присвоение не происходит, если регулярное выражение не является литералом.
re = /(?<lhs>\w+)\s*=\s*(?<rhs>\w+)/ re =~ " x = y " p lhs # undefined local variable p rhs # undefined local variable
Интерполяция регулярного выражения, #{}, также отключает присвоение.
rhs_pat = /(?<rhs>\w+)/
/(?<lhs>\w+)\s*=\s*#{rhs_pat}/ =~ "x = y"
p lhs # undefined local variable
Присвоение не происходит, если регулярное выражение расположено в правой части.
" x = y " =~ /(?<lhs>\w+)\s*=\s*(?<rhs>\w+)/ p lhs, rhs # undefined local variable
# File ext/json/lib/json/add/regexp.rb, line 17
def as_json(*)
{
JSON.create_id => self.class.name,
'o' => options,
's' => source,
}
end Возвращает хеш, который будет преобразован в объект JSON и представлять этот объект.
static VALUE
rb_reg_casefold_p(VALUE re)
{
rb_reg_check(re);
if (RREGEXP_PTR(re)->options & ONIG_OPTION_IGNORECASE) return Qtrue;
return Qfalse;
} Возвращает значение флага игнорирования регистра.
/a/.casefold? #=> false /a/i.casefold? #=> true /(?i:a)/.casefold? #=> false
VALUE
rb_obj_encoding(VALUE obj)
{
int idx = rb_enc_get_index(obj);
if (idx < 0) {
rb_raise(rb_eTypeError, "unknown encoding");
}
return rb_enc_from_encoding_index(idx & ENC_INDEX_MASK);
} Возвращает объект Encoding, представляющий кодировку объекта.
static VALUE
rb_reg_equal(VALUE re1, VALUE re2)
{
if (re1 == re2) return Qtrue;
if (!RB_TYPE_P(re2, T_REGEXP)) return Qfalse;
rb_reg_check(re1); rb_reg_check(re2);
if (FL_TEST(re1, KCODE_FIXED) != FL_TEST(re2, KCODE_FIXED)) return Qfalse;
if (RREGEXP_PTR(re1)->options != RREGEXP_PTR(re2)->options) return Qfalse;
if (RREGEXP_SRC_LEN(re1) != RREGEXP_SRC_LEN(re2)) return Qfalse;
if (ENCODING_GET(re1) != ENCODING_GET(re2)) return Qfalse;
if (memcmp(RREGEXP_SRC_PTR(re1), RREGEXP_SRC_PTR(re2), RREGEXP_SRC_LEN(re1)) == 0) {
return Qtrue;
}
return Qfalse;
} Равенство—Два объекта регулярных выражений равны, если их шаблоны идентичны, у них одинаковый код набора символов, и их casefold? значения совпадают.
/abc/ == /abc/x #=> false /abc/ == /abc/i #=> false /abc/ == /abc/u #=> false /abc/u == /abc/n #=> false
static VALUE
rb_reg_fixed_encoding_p(VALUE re)
{
if (FL_TEST(re, KCODE_FIXED))
return Qtrue;
else
return Qfalse;
} Возвращает false, если rxp применим к строке с любой кодировкой, совместимой с ASCII. Возвращает true в противном случае.
r = /a/
r.fixed_encoding? #=> false
r =~ "\u{6666} a" #=> 2
r =~ "\xa1\xa2 a".force_encoding("euc-jp") #=> 2
r =~ "abc".force_encoding("euc-jp") #=> 0
r = /a/u
r.fixed_encoding? #=> true
r.encoding #=> #<Encoding:UTF-8>
r =~ "\u{6666} a" #=> 2
r =~ "\xa1\xa2".force_encoding("euc-jp") #=> ArgumentError
r =~ "abc".force_encoding("euc-jp") #=> 0
r = /\u{6666}/
r.fixed_encoding? #=> true
r.encoding #=> #<Encoding:UTF-8>
r =~ "\u{6666} a" #=> 0
r =~ "\xa1\xa2".force_encoding("euc-jp") #=> ArgumentError
r =~ "abc".force_encoding("euc-jp") #=> nil
static VALUE
rb_reg_hash(VALUE re)
{
st_index_t hashval = reg_hash(re);
return ST2FIX(hashval);
} Создаёт хеш на основе текста и опций этого регулярного выражения.
См. также Object#hash.
static VALUE
rb_reg_inspect(VALUE re)
{
if (!RREGEXP_PTR(re) || !RREGEXP_SRC(re) || !RREGEXP_SRC_PTR(re)) {
return rb_any_to_s(re);
}
return rb_reg_desc(RREGEXP_SRC_PTR(re), RREGEXP_SRC_LEN(re), re);
} Генерирует красиво отформатированную строковую версию rxp. Возможно, удивительно, но #inspect фактически производит более естественный вид строки, чем #to_s.
/ab+c/ix.inspect #=> "/ab+c/ix"
static VALUE
rb_reg_match_m(int argc, VALUE *argv, VALUE re)
{
VALUE result, str, initpos;
long pos;
if (rb_scan_args(argc, argv, "11", &str, &initpos) == 2) {
pos = NUM2LONG(initpos);
}
else {
pos = 0;
}
pos = reg_match_pos(re, &str, pos);
if (pos < 0) {
rb_backref_set(Qnil);
return Qnil;
}
result = rb_backref_get();
rb_match_busy(result);
if (!NIL_P(result) && rb_block_given_p()) {
return rb_yield(result);
}
return result;
} Возвращает объект MatchData, описывающий совпадение, или nil , если совпадения не было. Это эквивалентно получению значения специальной переменной $~ после обычного сопоставления. Если второй параметр присутствует, он указывает позицию в строке для начала поиска.
/(.)(.)(.)/.match("abc")[2] #=> "b"
/(.)(.)/.match("abc", 1)[2] #=> "c"
Если указан блок, вызовите блок с MatchData, если сопоставление прошло успешно, так что вы можете написать
/M(.*)/.match("Matz") do |m|
puts m[0]
puts m[1]
end
вместо
if m = /M(.*)/.match("Matz")
puts m[0]
puts m[1]
end
В этом случае возвращаемое значение — результат выполнения блока.
static VALUE
rb_reg_match_m_p(int argc, VALUE *argv, VALUE re)
{
long pos = rb_check_arity(argc, 1, 2) > 1 ? NUM2LONG(argv[1]) : 0;
return rb_reg_match_p(re, argv[0], pos);
} Возвращает true или false , указывающее, соответствует ли регулярное выражение или нет без обновления $~ и других связанных переменных. Если второй параметр присутствует, он указывает позицию в строке для начала поиска.
/R.../.match?("Ruby") #=> true
/R.../.match?("Ruby", 1) #=> false
/P.../.match?("Ruby") #=> false
$& #=> nil
static VALUE
rb_reg_named_captures(VALUE re)
{
VALUE hash = rb_hash_new();
rb_reg_check(re);
onig_foreach_name(RREGEXP_PTR(re), reg_named_captures_iter, (void*)hash);
return hash;
} Возвращает хеш, представляющий информацию об именованных захватах rxp.
Ключ хеша — имя именованного захвата. Значение хеша — массив, который является списком индексов соответствующих именованных захватов.
/(?<foo>.)(?<bar>.)/.named_captures
#=> {"foo"=>[1], "bar"=>[2]}
/(?<foo>.)(?<foo>.)/.named_captures
#=> {"foo"=>[1, 2]}
Если именованных захватов нет, возвращается пустой хеш.
/(.)(.)/.named_captures
#=> {}
static VALUE
rb_reg_names(VALUE re)
{
VALUE ary;
rb_reg_check(re);
ary = rb_ary_new_capa(onig_number_of_names(RREGEXP_PTR(re)));
onig_foreach_name(RREGEXP_PTR(re), reg_names_iter, (void*)ary);
return ary;
} Возвращает список имён захватов в виде массива строк.
/(?<foo>.)(?<bar>.)(?<baz>.)/.names #=> ["foo", "bar", "baz"] /(?<foo>.)(?<foo>.)/.names #=> ["foo"] /(.)(.)/.names #=> []
static VALUE
rb_reg_options_m(VALUE re)
{
int options = rb_reg_options(re);
return INT2NUM(options);
} Возвращает набор битов, соответствующих опциям, используемым при создании этого Regexp (см. Regexp::new для получения подробностей. Обратите внимание, что дополнительные биты могут быть установлены в возвращаемых параметрах: они используются внутри кодом регулярных выражений. Эти дополнительные биты игнорируются, если опции передаются в Regexp::new.
Regexp::IGNORECASE #=> 1
Regexp::EXTENDED #=> 2
Regexp::MULTILINE #=> 4
/cat/.options #=> 0
/cat/ix.options #=> 3
Regexp.new('cat', true).options #=> 1
/\xa1\xa2/e.options #=> 16
r = /cat/ix
Regexp.new(r.source, r.options) #=> /cat/ix
static VALUE
rb_reg_source(VALUE re)
{
VALUE str;
rb_reg_check(re);
str = rb_str_dup(RREGEXP_SRC(re));
if (OBJ_TAINTED(re)) OBJ_TAINT(str);
return str;
} Возвращает исходную строку шаблона.
/ab+c/ix.source #=> "ab+c"
Обратите внимание, что последовательности escape сохраняются как есть.
/\x20\+/.source #=> "\\x20\\+"
# File ext/json/lib/json/add/regexp.rb, line 27 def to_json(*) as_json.to_json end
Сохраняет имя класса (Regexp) с опциями o и источником s (Regexp или Строка) в виде строки JSON.
static VALUE
rb_reg_to_s(VALUE re)
{
int options, opt;
const int embeddable = ONIG_OPTION_MULTILINE|ONIG_OPTION_IGNORECASE|ONIG_OPTION_EXTEND;
long len;
const UChar* ptr;
VALUE str = rb_str_buf_new2("(?");
char optbuf[5];
rb_encoding *enc = rb_enc_get(re);
rb_reg_check(re);
rb_enc_copy(str, re);
options = RREGEXP_PTR(re)->options;
ptr = (UChar*)RREGEXP_SRC_PTR(re);
len = RREGEXP_SRC_LEN(re);
again:
if (len >= 4 && ptr[0] == '(' && ptr[1] == '?') {
int err = 1;
ptr += 2;
if ((len -= 2) > 0) {
do {
opt = char_to_option((int )*ptr);
if (opt != 0) {
options |= opt;
}
else {
break;
}
++ptr;
} while (--len > 0);
}
if (len > 1 && *ptr == '-') {
++ptr;
--len;
do {
opt = char_to_option((int )*ptr);
if (opt != 0) {
options &= ~opt;
}
else {
break;
}
++ptr;
} while (--len > 0);
}
if (*ptr == ')') {
--len;
++ptr;
goto again;
}
if (*ptr == ':' && ptr[len-1] == ')') {
Regexp *rp;
VALUE verbose = ruby_verbose;
ruby_verbose = Qfalse;
++ptr;
len -= 2;
err = onig_new(&rp, ptr, ptr + len, ONIG_OPTION_DEFAULT,
enc, OnigDefaultSyntax, NULL);
onig_free(rp);
ruby_verbose = verbose;
}
if (err) {
options = RREGEXP_PTR(re)->options;
ptr = (UChar*)RREGEXP_SRC_PTR(re);
len = RREGEXP_SRC_LEN(re);
}
}
if (*option_to_str(optbuf, options)) rb_str_buf_cat2(str, optbuf);
if ((options & embeddable) != embeddable) {
optbuf[0] = '-';
option_to_str(optbuf + 1, ~options);
rb_str_buf_cat2(str, optbuf);
}
rb_str_buf_cat2(str, ":");
if (rb_enc_asciicompat(enc)) {
rb_reg_expr_str(str, (char*)ptr, len, enc, NULL);
rb_str_buf_cat2(str, ")");
}
else {
const char *s, *e;
char *paren;
ptrdiff_t n;
rb_str_buf_cat2(str, ")");
rb_enc_associate(str, rb_usascii_encoding());
str = rb_str_encode(str, rb_enc_from_encoding(enc), 0, Qnil);
/* backup encoded ")" to paren */
s = RSTRING_PTR(str);
e = RSTRING_END(str);
s = rb_enc_left_char_head(s, e-1, e, enc);
n = e - s;
paren = ALLOCA_N(char, n);
memcpy(paren, s, n);
rb_str_resize(str, RSTRING_LEN(str) - n);
rb_reg_expr_str(str, (char*)ptr, len, enc, NULL);
rb_str_buf_cat(str, paren, n);
}
rb_enc_copy(str, re);
OBJ_INFECT(str, re);
return str;
} Возвращает строку, содержащую регулярное выражение и его опции (используя обозначение (?opts:source)). Эта строка может быть повторно использована для создания регулярного выражения с той же семантикой, что и исходное. (Однако, Regexp#== может не вернуть true при сравнении двух, так как источник регулярного выражения может отличаться, как показано в примере). Regexp#inspect генерирует обычно более читабельную версию rxp.
r1 = /ab+c/ix #=> /ab+c/ix s1 = r1.to_s #=> "(?ix-m:ab+c)" r2 = Regexp.new(s1) #=> /(?ix-m:ab+c)/ r1 == r2 #=> false r1.source #=> "ab+c" r2.source #=> "(?ix-m:ab+c)"
VALUE
rb_reg_match2(VALUE re)
{
long start;
VALUE line = rb_lastline_get();
if (!RB_TYPE_P(line, T_STRING)) {
rb_backref_set(Qnil);
return Qnil;
}
start = rb_reg_search(re, line, 0, 0);
if (start < 0) {
return Qnil;
}
start = rb_str_sublen(line, start);
return LONG2FIX(start);
} Сопоставление—Сопоставляет rxp с содержимым $_. Эквивалентно rxp =~ $_.
$_ = "input data" ~ /at/ #=> 7
Ruby Core © 1993–2017 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.