класс 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- Совпадение с точкой, где закончилось последнее совпадение -
\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 квантифицируется как непосредственным +, так и окружающим *, не имея ничего, что бы различало, что контролирует тот или иной символ. Возникающий недетерминизм приводит к производительности, зависящей от квадрата времени. (См. Mastering Regular Expressions (3-е изд.), стр. 222, Jeffery Friedl, для углубленного анализа). Этот конкретный случай можно исправить, используя атомарную группировку, которая предотвращает необоснованный возврат:
(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('\*?{}.') #=> \\\*\?\{\}\.
# File ext/json/lib/json/add/regexp.rb, line 11 def self.json_create(object) new(object['s'], object['o']) end
Десериализует JSON-строку, создавая новый объект Regexp с исходным s (Regexp или String) и параметрами o сериализованными to_json
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)
{
onig_errmsg_buffer err = "";
int flags = 0;
VALUE str;
rb_encoding *enc;
const char *ptr;
long len;
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);
ptr = RREGEXP_SRC_PTR(re);
len = RREGEXP_SRC_LEN(re);
enc = rb_enc_get(re);
if (rb_reg_initialize(self, ptr, len, enc, flags, err, NULL, 0)) {
str = rb_enc_str_new(ptr, len, enc);
rb_reg_raise_str(str, flags, err);
}
}
else {
if (argc >= 2) {
if (FIXNUM_P(argv[1])) flags = FIX2INT(argv[1]);
else if (RTEST(argv[1])) flags = ONIG_OPTION_IGNORECASE;
}
enc = 0;
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 = argv[0];
ptr = StringValuePtr(str);
if (enc
? rb_reg_initialize(self, ptr, RSTRING_LEN(str), enc, flags, err, NULL, 0)
: rb_reg_initialize_str(self, str, flags, err, NULL, 0)) {
rb_reg_raise_str(str, flags, err);
}
}
return self;
} Создаёт новый регулярный выраз из pattern, который может быть либо строкой, либо Regexp (в этом случае параметры regexp будут переданы), и новые параметры не могут быть указаны (изменение с Ruby 1.8).
Если options является Fixnum, то это должна быть одна или несколько констант Regexp::EXTENDED, Regexp::IGNORECASE и Regexp::MULTILINE, объединённые через побитовое ИЛИ. В противном случае, если 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. Возвращает преобразованное регулярное выражение или 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, который является объединением заданных шаблонов, то есть будет соответствовать любой его части. Шаблоны могут быть объектами 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(re1)->ptr->options != RREGEXP(re2)->ptr->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;
} Равенство — Два объекта regexp равны, если их шаблоны идентичны, у них одинаковый код набора символов, и их 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
Если =~ используется с литералом regexp с именованными группами захвата, захваченные строки (или 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-literal =~ expression' для присваивания. Регулярное выражение должно быть литералом без интерполяции и стоять в левой части выражения.
Присваивание не происходит, если регулярное выражение не является литералом.
re = /(?<lhs>\w+)\s*=\s*(?<rhs>\w+)/ re =~ " x = y " p lhs # undefined local variable p rhs # undefined local variable
Интерполяция regexp, #{}, также отключает присваивание.
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(re)->ptr->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(re1)->ptr->options != RREGEXP(re2)->ptr->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;
} Равенство — Два объекта regexp равны, если их шаблоны идентичны, у них одинаковый код набора символов, и их 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 LONG2FIX(hashval);
} Генерирует хеш, основанный на тексте и параметрах этого регулярного выражения.
См. также Object#hash.
static VALUE
rb_reg_inspect(VALUE re)
{
if (!RREGEXP(re)->ptr || !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_named_captures(VALUE re)
{
VALUE hash = rb_hash_new();
rb_reg_check(re);
onig_foreach_name(RREGEXP(re)->ptr, 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_ary_new();
rb_reg_check(re);
onig_foreach_name(RREGEXP(re)->ptr, 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_enc_str_new(RREGEXP_SRC_PTR(re),RREGEXP_SRC_LEN(re), rb_enc_get(re));
if (OBJ_TAINTED(re)) OBJ_TAINT(str);
return str;
} Возвращает исходную строку шаблона.
/ab+c/ix.source #=> "ab+c"
Обратите внимание, что управляющие последовательности сохраняются как есть.
/\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 или String) в виде строки 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(re)->ptr->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(re)->ptr->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::new для создания регулярного выражения с той же семантикой, что и исходное. (Однако, 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.