класс 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, поэтому она соответствует стог_сена тоже.
=~ и Regexp#match
Сопоставление с образцом можно выполнить, используя оператор =~ или метод Regexp#match.
=~ оператор
=~ — это базовый оператор сопоставления с образцом в Ruby. Когда один операнд — регулярное выражение, а другой — строка, то регулярное выражение используется как шаблон для сопоставления со строкой. (Этот оператор эквивалентно определён Regexp и String, поэтому порядок String и Regexp не важен. У других классов могут быть разные реализации =~.) Если совпадение найдено, оператор возвращает индекс первого совпадения в строке, в противном случае возвращает nil.
/hay/ =~ 'haystack' #=> 0 'haystack' =~ /hay/ #=> 0 /a/ =~ 'haystack' #=> 1 /u/ =~ 'haystack' #=> nil
Используя оператор =~ со String и Regexp, переменная $~ устанавливается после успешного сопоставления. $~ содержит объект MatchData. Regexp.last_match эквивалентно $~.
Regexp#match метод
Метод match возвращает объект MatchData:
/st/.match('haystack') #=> #<MatchData "st">
Метасимволы и экранирование
Следующие являются метасимволами (, ), [, ], {, }, ., ?, +, *. Они имеют определённое значение при появлении в шаблоне. Чтобы буквально сопоставить их, необходимо экранировать их обратным слэшем. Чтобы буквально сопоставить обратный слэш, необходимо экранировать его обратным слэшем: \\.
/1 \+ 2 = 3\?/.match('Does 1 + 2 = 3?') #=> #<MatchData "1 + 2 = 3?">
/a\\\\b/.match('a\\\\b') #=> #<MatchData "a\\b">
Шаблоны ведут себя как строки в двойных кавычках и могут содержать те же экранированные последовательности обратного слэша (значение \s отличается, см. ниже).
/\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]/ -
/\R/- Символ новой строки:\n,\v,\f,\r\u0085(следующая строка),\u2028(разделитель строк),\u2029(разделитель абзацев) или\r\n.
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">
Повторение по умолчанию жадное: соответствует максимально возможному количеству вхождений, всё ещё позволяя общему совпадению быть успешным. В отличие от этого, ленивое сопоставление использует минимальное количество совпадений, необходимое для общего успеха. Большинство жадных метасимволов можно сделать ленивыми, добавив за ними ?. Для шаблона {n}, поскольку он определяет точное количество символов для сопоставления, а не переменное число символов, метасимвол ? вместо этого делает повторяемый шаблон необязательным.
Оба шаблона ниже соответствуют строке. Первый использует жадный квантификатор, поэтому '.+' соответствует '<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">
Regexp#match возвращает объект MatchData, который делает доступным захваченный текст с помощью метода []:
/[csh](..) [csh]\1 in/.match("The cat sat in the hat")[1] #=> 'at'
Группы захвата могут ссылаться по имени, если определены с помощью конструкции (?<имя>) или (?'имя').
/\$(?<dollars>\d+)\.(?<cents>\d+)/.match("$3.67")
#=> #<MatchData "$3.67" dollars:"3" cents:"67">
/\$(?<dollars>\d+)\.(?<cents>\d+)/.match("$3.67")[:dollars] #=> "3"
Имена групп могут быть обработаны с помощью \k<имя>, где имя — это имя группы.
/(?<vowel>[aeiou]).\k<vowel>.\k<vowel>/.match('ototomy')
#=> #<MatchData "ototo" vowel:"o">
Примечание: один и тот же регулярный выражения не может одновременно использовать именованные и порядковые обратные ссылки. Кроме того, если в регулярном выражении используется именованный захват, то круглые скобки, используемые для группировки, которые в противном случае привели бы к безымянному захвату, обрабатываются как незахватывающие.
/(\w)(\w)/.match("ab").captures # => ["a", "b"]
/(\w)(\w)/.match("ab").named_captures # => {}
/(?<c>\w)(\w)/.match("ab").captures # => ["a"]
/(?<c>\w)(\w)/.match("ab").named_captures # => {"c"=>"a"}
Когда именованные группы захвата используются с литеральным регулярным выражением слева от выражения и оператором =~, захваченный текст также присваивается локальным переменным с соответствующими именами.
/\$(?<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">
Атомарное группирование
Группирование может быть сделано атомарным с помощью (?>шаблон). Это заставляет подвыражение шаблон соответствовать независимо от остальной части выражения, таким образом, то, что оно соответствует, фиксируется для оставшейся части сопоставления, если только всё подвыражение не должно быть оставлено и впоследствии пересмотрено. Таким образом, шаблон обрабатывается как неделимое целое. Атомарное группирование обычно используется для оптимизации шаблонов, чтобы предотвратить ненужное возвращение к предыдущим позициям в регулярном выражении.
" в шаблоне ниже соответствует первому символу строки, а затем .* соответствует Quote“. Это приводит к отказу от всего сопоставления, поэтому текст, сопоставленный .*, возвращается на одну позицию назад, что делает доступным последний символ строки для сопоставления ".
/".*"/.match('"Quote"') #=> #<MatchData "\"Quote\"">
Если .* сгруппирован атомарно, он отказывается от возврата к Quote“, даже если это означает, что всё сопоставление провалится.
/"(?>.*)"/.match('"Quote"') #=> nil
Вызовы подвыражений
Синтаксис \g<имя> соответствует предыдущему подвыражению с именем имя, которое может быть именем группы или номером, ещё раз. Это отличается от обратных ссылок тем, что оно повторно выполняет группу, а не просто пытается повторно сопоставить тот же текст.
Этот шаблон соответствует символу ( и присваивает его группе 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: Буква, Знак, Число, Разделительный знак препинания -
/\p{ASCII}/- Символ ASCII -
/\p{Any}/- Любой символ Unicode (включая неназначенные символы) -
/\p{Assigned}/- Назначенный символ
Значение Категории универсального символа Unicode также может быть сопоставлено с \p{Ab}, где Ab — это сокращение категории, как описано ниже:
Последний \p{} соответствует алфавиту Unicode символа. Поддерживаются следующие алфавиты: арабский, армянский, балийский, бенгальский, бопомофо, шрифт Брайля, бугинийский, бухид, канадский аборигенный, карийский, чамский, чернокожий, общий, коптский, клинописный, кипрский, кириллический, дезерет, деванагари, эфиопский, грузинский, глаголический, готический, греческий, гуджарати, гурумухи, хань, хангуль, хануну, иврит, хирагана, унаследованный, каннада, катакана, каях ли, харошти, кхмерский, лаосский, латинский, лепка, лимбу, линейный Б, ликийский, лидийский, малаялам, монгольский, бирманский, новый тай люе, нко, огам, ол чики, древнеиталийский, древнеперсидский, ория, османский, фагс па, финикийский, режанг, рунический, саурасhtra, шавиан, сингальский, сунданесский, силоти нагри, сирийский, тагалог, тагбануа, тай ле, тамильский, телугу, тана, тайский, тибетский, тифинаг, угаритский, вай и и.
Кодовый символ 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/i.match('ABC') #=> nil
Кроме того, эти параметры также могут быть переключены для остальной части шаблона:
/a(?i)bc/.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 не могут содержать неэкранированные символы-разделители.
Encoding
Регулярные выражения предполагают использование кодировки источника. Это может быть переопределено одним из следующих модификаторов.
-
/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"
# raises Encoding::CompatibilityError: incompatible encoding regexp match
# (ISO-8859-1 regexp with UTF-8 string)
Специальные глобальные переменные
Сопоставление шаблона устанавливает некоторые глобальные переменные:
-
$~эквивалентноRegexp.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
Константы
- EXTENDED
-
см.
Regexp.optionsиRegexp.new - FIXEDENCODING
-
см.
Regexp.optionsиRegexp.new - IGNORECASE
-
см.
Regexp.optionsиRegexp.new - MULTILINE
-
см.
Regexp.optionsиRegexp.new - NOENCODING
-
см.
Regexp.optionsиRegexp.new
Методы публичного класса
Псевдоним для Regexp.new
static VALUE
rb_reg_s_quote(VALUE c, VALUE str)
{
return rb_reg_quote(reg_operand(str, TRUE));
} Экранирует любые символы, которые имели бы специальное значение в регулярном выражении. Возвращает новую экранированную строку с тем же или совместимым кодированием. Для любой строки Regexp.new(Regexp.escape(str))=~str будет истинно.
Regexp.escape('\*?{}.') #=> \\\*\?\{\}\.
static VALUE
rb_reg_s_last_match(int argc, VALUE *argv, VALUE _)
{
if (rb_check_arity(argc, 0, 1) == 1) {
VALUE match = rb_backref_get();
int n;
if (NIL_P(match)) return Qnil;
n = match_backref_number(match, argv[0]);
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, которое может быть либо String, либо Regexp (в этом случае опции этого регулярного выражения передаются), и новые опции не могут быть указаны (изменение, начиная с Ruby 1.8).
Если options является Integer, это должно быть одно или несколько констант 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));
} Экранирует любые символы, которые имели бы специальное значение в регулярном выражении. Возвращает новую экранированную строку с тем же или совместимым кодированием. Для любой строки 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_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[a-z]*\z/; print "Lower case\n" when /\A[A-Z]*\z/; print "Upper case\n" else; print "Mixed case\n" end #=> "Upper case"
Использование объекта регулярного выражения с оператором === позволяет сравнивать его с String.
/^[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-literal =~ expression' для присваивания. Регулярное выражение должно быть литералом без интерполяции и расположено в левой части.
Присваивание не выполняется, если регулярное выражение не является литералом.
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") #=> Encoding::CompatibilityError
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") #=> Encoding::CompatibilityError
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)
{
regex_t *reg = (rb_reg_check(re), RREGEXP_PTR(re));
VALUE hash = rb_hash_new_with_size(onig_number_of_names(reg));
onig_foreach_name(reg, 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));
return str;
} Возвращает исходную строку шаблона.
/ab+c/ix.source #=> "ab+c"
Обратите внимание, что escape-последовательности сохраняются как есть.
/\x20\+/.source #=> "\\x20\\+"
static VALUE
rb_reg_to_s(VALUE re)
{
return rb_reg_str_with_term(re, '/');
} Возвращает строку, содержащую регулярное выражение и его опции (с использованием обозначения (?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.