Spec-Zone.ru › Ruby 4.0

класс Regexp

Родительский класс:
Object

Регулярное выражение (также называемое regexp) — это шаблон совпадения (также просто называемый шаблоном).

Распространённая запись regexp использует символы слеша в качестве ограничителей:

/foo/

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

re = /red/
re.match?('redirect') # => true   # Match at beginning of target.
re.match?('bored')    # => true   # Match at end of target.
re.match?('credit')   # => true   # Match within target.
re.match?('foo')      # => false  # No match.

Использование Regexp

Regexp можно использовать:

  • Для извлечения подстрок на основе заданного шаблона:

    re = /foo/              # => /foo/
    re.match('food')        # => #<MatchData "foo">
    re.match('good')        # => nil
    

    См. разделы Метод match и Оператор =~.

  • Для определения того, соответствует ли строка заданному шаблону:

    re.match?('food') # => true
    re.match?('good') # => false
    

    См. раздел Метод match?.

  • В качестве аргумента при вызове некоторых методов других классов и модулей; большинство таких методов принимают аргумент, которым может быть строка или (гораздо более мощный) regexp.

    См. Методы Regexp.

Объекты Regexp

Объект regexp имеет:

  • Источник; см. раздел Источники.

  • Несколько режимов; см. раздел Режимы.

  • Время ожидания; см. раздел Время ожидания.

  • Кодировку; см. раздел Кодировки.

Создание Regexp

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

  • Литерала regexp со слешами (см. раздел Литералы Regexp):

    # This is a very common usage.
    /foo/ # => /foo/
    
  • Литерала regexp %r (см. раздел %r: литералы Regexp):

    # Same delimiter character at beginning and end;
    # useful for avoiding escaping characters
    %r/name\/value pair/ # => /name\/value pair/
    %r:name/value pair:  # => /name\/value pair/
    %r|name/value pair|  # => /name\/value pair/
    
    # Certain "paired" characters can be delimiters.
    %r[foo] # => /foo/
    %r{foo} # => /foo/
    %r(foo) # => /foo/
    %r<foo> # => /foo/
    
  • Метода Regexp.new.

Метод match

Каждый из методов Regexp#match, String#match и Symbol#match возвращает объект MatchData, если совпадение найдено, и nil в противном случае; каждый также устанавливает глобальные переменные:

'food'.match(/foo/) # => #<MatchData "foo">
'food'.match(/bar/) # => nil

Оператор =~

Каждый из операторов Regexp#=~, String#=~ и Symbol#=~ возвращает целочисленное смещение, если совпадение найдено, и nil в противном случае; каждый также устанавливает глобальные переменные:

/bar/ =~ 'foo bar' # => 4
'foo bar' =~ /bar/ # => 4
/baz/ =~ 'foo bar' # => nil

Метод match?

Каждый из методов Regexp#match?, String#match? и Symbol#match? возвращает true, если совпадение найдено, и false в противном случае; ни один из них не устанавливает глобальные переменные:

'food'.match?(/foo/) # => true
'food'.match?(/bar/) # => false

Глобальные переменные

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

  • match: см. раздел Метод match.

  • =~: см. раздел Оператор =~.

Затрагиваемые глобальные переменные:

  • $~: возвращает объект MatchData или nil.

  • $&: возвращает совпавшую часть строки или nil.

  • $`: возвращает часть строки слева от совпадения или nil.

  • $': возвращает часть строки справа от совпадения или nil.

  • $+: возвращает последнюю совпавшую группу или nil.

  • $1, $2 и т. д.: возвращает первую, вторую и последующие совпавшие группы или nil. Обратите внимание, что $0 — совсем другое: она возвращает имя выполняемой в данный момент программы.

Эти переменные, за исключением $~, являются сокращёнными обозначениями методов $~. См. раздел Эквивалентность глобальных переменных в MatchData.

Примеры:

# Matched string, but no matched groups.
'foo bar bar baz'.match('bar')
$~ # => #<MatchData "bar">
$& # => "bar"
$` # => "foo "
$' # => " bar baz"
$+ # => nil
$1 # => nil

# Matched groups.
/s(\w{2}).*(c)/.match('haystack')
$~ # => #<MatchData "stac" 1:"ta" 2:"c">
$& # => "stac"
$` # => "hay"
$' # => "k"
$+ # => "c"
$1 # => "ta"
$2 # => "c"
$3 # => nil

# No match.
'foo'.match('bar')
$~ # => nil
$& # => nil
$` # => nil
$' # => nil
$+ # => nil
$1 # => nil

Обратите внимание, что методы Regexp#match?, String#match? и Symbol#match? не устанавливают глобальные переменные.

Источники

Как видно выше, в простейшем regexp в качестве источника используется литерал выражения:

re = /foo/              # => /foo/
re.match('food')        # => #<MatchData "foo">
re.match('good')        # => nil

Богатый набор доступных подвыражений придаёт regexp широкие возможности и гибкость:

  • Специальные символы

  • Литералы источника

  • Классы символов

  • Сокращённые классы символов

  • Якоря

  • Альтернатива

  • Квантификаторы

  • Группы и захваты

  • Юникод

  • Выражения в скобках POSIX

  • Комментарии

Специальные символы

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

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

Чтобы найти метасимвол как обычный символ, экранируйте его обратной косой чертой:

# Matches one or more 'o' characters.
/o+/.match('foo')  # => #<MatchData "oo">
# Would match 'o+'.
/o\+/.match('foo') # => nil

Чтобы найти обратную косую черту как обычный символ, экранируйте её обратной косой чертой:

/\./.match('\.')  # => #<MatchData ".">
/\\./.match('\.') # => #<MatchData "\\.">

Метод Regexp.escape возвращает экранированную строку:

Regexp.escape('.?-+*^\|$()[]{}')
# => "\\.\\?\\-\\+\\*\\^\\\\\\|\\$\\(\\)\\[\\]\\{\\}"

Литералы источника

Литерал источника во многом ведёт себя как строка в двойных кавычках; см. раздел Строковые литералы в двойных кавычках.

В частности, литерал источника может содержать интерполированные выражения:

s = 'foo'         # => "foo"
/#{s}/            # => /foo/
/#{s.capitalize}/ # => /Foo/
/#{2 + 2}/        # => /4/

Обычный строковый литерал и литерал источника различаются; см. раздел Сокращённые классы символов.

  • \s в обычном строковом литерале эквивалентно пробелу; в литерале источника это сокращённая запись для сопоставления с пробельным символом.

  • В обычном строковом литерале эти символы (избыточно) экранированы; в литерале источника они являются сокращёнными обозначениями различных символов для сопоставления:

    \w \W \d \D \h \H \S \R

Классы символов

Класс символов заключается в квадратные скобки; он указывает, какие символы совпадают в заданной позиции целевой строки:

# This character class will match any vowel.
re = /B[aeiou]rd/
re.match('Bird') # => #<MatchData "Bird">
re.match('Bard') # => #<MatchData "Bard">
re.match('Byrd') # => nil

В классе символов можно использовать дефисы для задания диапазонов символов:

# These regexps have the same effect.
/[abcdef]/.match('foo') # => #<MatchData "f">
/[a-f]/.match('foo')    # => #<MatchData "f">
/[a-cd-f]/.match('foo') # => #<MatchData "f">

Если первым символом класса является знак вставки (^), смысл класса инвертируется: он совпадает с любым символом, кроме указанных.

/[^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]/

Сокращённые классы символов

Каждый из следующих метасимволов служит сокращённым обозначением класса символов:

  • /./: соответствует любому символу, кроме символа новой строки:

    /./.match('foo') # => #<MatchData "f">
    /./.match("\n")  # => nil
    
  • /./m: соответствует любому символу, включая символ новой строки; см. раздел Многострочный режим:

    /./m.match("\n") # => #<MatchData "\n">
    
  • /\w/: соответствует символу слова; эквивалентно [a-zA-Z0-9_]:

    /\w/.match(' foo') # => #<MatchData "f">
    /\w/.match(' _')   # => #<MatchData "_">
    /\w/.match(' ')    # => nil
    
  • /\W/: соответствует символу, не являющемуся символом слова; эквивалентно [^a-zA-Z0-9_]:

    /\W/.match(' ') # => #<MatchData " ">
    /\W/.match('_') # => nil
    
  • /\d/: соответствует цифре; эквивалентно [0-9]:

    /\d/.match('THX1138') # => #<MatchData "1">
    /\d/.match('foo')     # => nil
    
  • /\D/: соответствует символу, не являющемуся цифрой; эквивалентно [^0-9]:

    /\D/.match('123Jump!') # => #<MatchData "J">
    /\D/.match('123')      # => nil
    
  • /\h/: соответствует шестнадцатеричному символу; эквивалентно [0-9a-fA-F]:

    /\h/.match('xyz fedcba9876543210') # => #<MatchData "f">
    /\h/.match('xyz')                  # => nil
    
  • /\H/: соответствует символу, не являющемуся шестнадцатеричным; эквивалентно [^0-9a-fA-F]:

    /\H/.match('fedcba9876543210xyz') # => #<MatchData "x">
    /\H/.match('fedcba9876543210')    # => nil
    
  • /\s/: соответствует пробельному символу; эквивалентно /[ \t\r\n\f\v]/:

    /\s/.match('foo bar') # => #<MatchData " ">
    /\s/.match('foo')     # => nil
    
  • /\S/: соответствует непробельному символу; эквивалентно /[^ \t\r\n\f\v]/:

    /\S/.match(" \t\r\n\f\v foo") # => #<MatchData "f">
    /\S/.match(" \t\r\n\f\v")     # => nil
    
  • /\R/: соответствует символу переноса строки независимо от платформы:

    /\R/.match("\r")     # => #<MatchData "\r">     # Carriage return (CR)
    /\R/.match("\n")     # => #<MatchData "\n">     # Newline (LF)
    /\R/.match("\f")     # => #<MatchData "\f">     # Formfeed (FF)
    /\R/.match("\v")     # => #<MatchData "\v">     # Vertical tab (VT)
    /\R/.match("\r\n")   # => #<MatchData "\r\n">   # CRLF
    /\R/.match("\u0085") # => #<MatchData "\u0085"> # Next line (NEL)
    /\R/.match("\u2028") # => #<MatchData "\u2028"> # Line separator (LSEP)
    /\R/.match("\u2029") # => #<MatchData "\u2029"> # Paragraph separator (PSEP)
    

Якоря

Якорь — это метапоследовательность, соответствующая позиции нулевой ширины между символами целевой строки.

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

/real/.match('surrealist') # => #<MatchData "real">

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

Граничные якоря

Каждый из этих якорей соответствует границе:

  • ^: соответствует началу строки:

    /^bar/.match("foo\nbar") # => #<MatchData "bar">
    /^ar/.match("foo\nbar")  # => nil
    
  • $: соответствует концу строки:

    /bar$/.match("foo\nbar") # => #<MatchData "bar">
    /ba$/.match("foo\nbar")  # => nil
    
  • \A: соответствует началу строки:

    /\Afoo/.match('foo bar')  # => #<MatchData "foo">
    /\Afoo/.match(' foo bar') # => nil
    
  • \Z: соответствует концу строки; если строка заканчивается одним символом новой строки, соответствует позиции перед ним:

    /foo\Z/.match('bar foo')     # => #<MatchData "foo">
    /foo\Z/.match('foo bar')     # => nil
    /foo\Z/.match("bar foo\n")   # => #<MatchData "foo">
    /foo\Z/.match("bar foo\n\n") # => nil
    
  • \z: соответствует концу строки:

    /foo\z/.match('bar foo')   # => #<MatchData "foo">
    /foo\z/.match('foo bar')   # => nil
    /foo\z/.match("bar foo\n") # => nil
    
  • \b: вне скобок соответствует границе слова; внутри скобок соответствует символу возврата на шаг ("0x08"):

    /foo\b/.match('foo bar') # => #<MatchData "foo">
    /foo\b/.match('foobar')  # => nil
    
  • \B: соответствует позиции, не являющейся границей слова:

    /foo\B/.match('foobar')  # => #<MatchData "foo">
    /foo\B/.match('foo bar') # => nil
    
  • \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
    

Якоря просмотра

Якоря просмотра вперёд:

  • (?=pat): утвердительное условие просмотра вперёд: проверяет, что следующие символы соответствуют pat, но не включает эти символы в совпавшую подстроку.

  • (?!pat): отрицательное условие просмотра вперёд: проверяет, что следующие символы не соответствуют pat, но не включает эти символы в совпавшую подстроку.

Якоря просмотра назад:

  • (?<=pat): утвердительное условие просмотра назад: проверяет, что предшествующие символы соответствуют pat, но не включает эти символы в совпавшую подстроку.

  • (?<!pat): отрицательное условие просмотра назад: проверяет, что предшествующие символы не соответствуют pat, но не включает эти символы в совпавшую подстроку.

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

/(?<=<b>)\w+(?=<\/b>)/.match("Fortune favors the <b>bold</b>.")
# => #<MatchData "bold">

Шаблон в просмотре назад должен иметь фиксированную ширину. Однако варианты на верхнем уровне могут иметь разную длину. Например, (?<=a|bc) допустимо. (?<=aaa(?:b|cd)) не допускается.

Якорь сброса совпадения

  • \K: сброс совпадения: совпавшее содержимое перед \K в regexp исключается из результата. Например, следующие два regexp почти эквивалентны:

    /ab\Kc/.match('abc')    # => #<MatchData "c">
    /(?<=ab)c/.match('abc') # => #<MatchData "c">
    

    Они совпадают с одной и той же строкой, и $& равно 'c', но позиция совпадения различается.

    То же относится к следующим двум regexp:

    /(a)\K(b)\Kc/
    /(?<=(?<=(a))(b))c/
    

Альтернатива

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

Две альтернативы:

re = /(a|b)/
re.match('foo') # => nil
re.match('bar') # => #<MatchData "b" 1:"b">

Четыре альтернативы:

re = /(a|b|c|d)/
re.match('shazam') # => #<MatchData "a" 1:"a">
re.match('cold')   # => #<MatchData "c" 1:"c">

Каждая альтернатива является подвыражением и может состоять из других подвыражений:

re = /([a-c]|[x-z])/
re.match('bar') # => #<MatchData "b" 1:"b">
re.match('ooz') # => #<MatchData "z" 1:"z">

Метод Regexp.union позволяет удобно создавать regexp с альтернативами.

Квантификаторы

Простой regexp соответствует одному символу:

/\w/.match('Hello')  # => #<MatchData "H">

Добавленный квантификатор задаёт, сколько совпадений требуется или допускается:

  • * — соответствует ноль или более раз:

    /\w*/.match('')
    # => #<MatchData "">
    /\w*/.match('x')
    # => #<MatchData "x">
    /\w*/.match('xyz')
    # => #<MatchData "xyz">
    
  • + — соответствует один или более раз:

    /\w+/.match('')    # => nil
    /\w+/.match('x')   # => #<MatchData "x">
    /\w+/.match('xyz') # => #<MatchData "xyz">
    
  • ? — соответствует ноль или один раз:

    /\w?/.match('')    # => #<MatchData "">
    /\w?/.match('x')   # => #<MatchData "x">
    /\w?/.match('xyz') # => #<MatchData "x">
    
  • {n} — соответствует ровно n раз:

    /\w{2}/.match('')    # => nil
    /\w{2}/.match('x')   # => nil
    /\w{2}/.match('xyz') # => #<MatchData "xy">
    
  • {min,} — соответствует min или более раз:

    /\w{2,}/.match('')    # => nil
    /\w{2,}/.match('x')   # => nil
    /\w{2,}/.match('xy')  # => #<MatchData "xy">
    /\w{2,}/.match('xyz') # => #<MatchData "xyz">
    
  • {,max} — соответствует max раз или меньше:

    /\w{,2}/.match('')    # => #<MatchData "">
    /\w{,2}/.match('x')   # => #<MatchData "x">
    /\w{,2}/.match('xyz') # => #<MatchData "xy">
    
  • {min,max} — соответствует не менее min и не более max раз:

    /\w{1,2}/.match('')    # => nil
    /\w{1,2}/.match('x')   # => #<MatchData "x">
    /\w{1,2}/.match('xyz') # => #<MatchData "xy">
    

Жадное, ленивое или обладающее квантификатором сопоставление

Сопоставление с квантификатором может быть жадным, ленивым или обладающим квантификатором:

  • При жадном сопоставлении учитывается максимально возможное число вхождений, при котором общее совпадение всё ещё успешно. Жадные квантификаторы: *, +, ?, {min, max} и их варианты.

  • При ленивом сопоставлении учитывается минимально возможное число вхождений. Ленивые квантификаторы: *?, +?, ??, {min, max}? и их варианты.

  • При обладающем квантификатором сопоставлении после обнаружения совпадения возврат невозможен; это совпадение сохраняется, даже если из-за него общее совпадение окажется неуспешным. Квантификаторы с таким свойством: *+, ++, ?+. Обратите внимание, что {min, max} и их варианты не поддерживают такое сопоставление.

Подробнее:

  • О жадном и ленивом сопоставлении см. Выбор минимального или максимального повторения.

  • О сопоставлении с квантификаторами, запрещающими возврат, см. Исключение ненужного возврата.

Группы и захваты

Простой regexp имеет (не более) одного совпадения:

re = /\d\d\d\d-\d\d-\d\d/
re.match('1943-02-04')      # => #<MatchData "1943-02-04">
re.match('1943-02-04').size # => 1
re.match('foo')             # => nil

Добавление одной или нескольких пар круглых скобок, (subexpression), задаёт группы, в результате чего могут совпасть несколько подстрок, называемых захватами:

re = /(\d\d\d\d)-(\d\d)-(\d\d)/
re.match('1943-02-04')      # => #<MatchData "1943-02-04" 1:"1943" 2:"02" 3:"04">
re.match('1943-02-04').size # => 4

Первый захват — это вся совпавшая строка; остальные захваты — совпавшие подстроки из групп.

У группы может быть квантификатор:

re = /July 4(th)?/
re.match('July 4')   # => #<MatchData "July 4" 1:nil>
re.match('July 4th') # => #<MatchData "July 4th" 1:"th">

re = /(foo)*/
re.match('')       # => #<MatchData "" 1:nil>
re.match('foo')    # => #<MatchData "foo" 1:"foo">
re.match('foofoo') # => #<MatchData "foofoo" 1:"foo">

re = /(foo)+/
re.match('')       # => nil
re.match('foo')    # => #<MatchData "foo" 1:"foo">
re.match('foofoo') # => #<MatchData "foofoo" 1:"foo">

Объект MatchData предоставляет доступ к совпавшим подстрокам:

re = /(\d\d\d\d)-(\d\d)-(\d\d)/
md = re.match('1943-02-04')
# => #<MatchData "1943-02-04" 1:"1943" 2:"02" 3:"04">
md[0] # => "1943-02-04"
md[1] # => "1943"
md[2] # => "02"
md[3] # => "04"

Незахватывающие группы

Группу можно сделать незахватывающей; она всё равно остаётся группой (и, например, может иметь квантификатор), но её совпавшая подстрока не включается в захваты.

Незахватывающая группа начинается с ?: (внутри круглых скобок):

# Don't capture the year.
re = /(?:\d\d\d\d)-(\d\d)-(\d\d)/
md = re.match('1943-02-04') # => #<MatchData "1943-02-04" 1:"02" 2:"04">

Обратные ссылки

На совпадение группы можно ссылаться и внутри самого regexp; такая ссылка называется backreference:

/[csh](..) [csh]\1 in/.match('The cat sat in the hat')
# => #<MatchData "cat sat in" 1:"at">

В этой таблице показано, какой подстроке целевой строки соответствует каждое подвыражение приведённого выше regexp:

| Subexpression in Regexp   | Matching Substring in Target String |
|---------------------------|-------------------------------------|
|       First '[csh]'       |            Character 'c'            |
|          '(..)'           |        First substring 'at'         |
|      First space ' '      |      First space character ' '      |
|       Second '[csh]'      |            Character 's'            |
| '\1' (backreference 'at') |        Second substring 'at'        |
|           ' in'           |            Substring ' in'          |

Regexp может содержать любое количество групп:

  • Для большого количества групп:

    • Обычная запись \n применима только для n в диапазоне (1..9).

    • Запись MatchData[n] применима для любого неотрицательного n.

  • \0 — специальная обратная ссылка на всю совпавшую строку; её нельзя использовать внутри самого regexp, но можно использовать вне его (например, при вызове метода подстановки):

    'The cat sat in the hat'.gsub(/[csh]at/, '\0s')
    # => "The cats sats in the hats"
    

Именованные захваты

Как видно выше, на захват можно ссылаться по его номеру. Захвату также можно присвоить имя, добавив перед ним ?<name> или ?'name'; это имя (преобразованное в символ) можно использовать в качестве индекса в MatchData[]:

md = /\$(?<dollars>\d+)\.(?'cents'\d+)/.match("$3.67")
# => #<MatchData "$3.67" dollars:"3" cents:"67">
md[:dollars]  # => "3"
md[:cents]    # => "67"
# The capture numbers are still valid.
md[2]         # => "67"

Если regexp содержит именованный захват, безымянных захватов в нём нет:

/\$(?<dollars>\d+)\.(\d+)/.match("$3.67")
# => #<MatchData "$3.67" dollars:"3">

На именованную группу можно сослаться с помощью \k<name>:

/(?<vowel>[aeiou]).\k<vowel>.\k<vowel>/.match('ototomy')
# => #<MatchData "ototo" vowel:"o">

Если (и только если) regexp содержит именованные группы захвата и стоит перед оператором =~, захваченные подстроки присваиваются локальным переменным с соответствующими именами:

/\$(?<dollars>\d+)\.(?<cents>\d+)/ =~ '$3.67'
dollars # => "3"
cents   # => "67"

Метод Regexp#named_captures возвращает хеш имён захватов и подстрок; метод Regexp#names возвращает массив имён захватов.

Атомарная группировка

Группу можно сделать атомарной с помощью (?>подвыражение).

В результате подвыражение сопоставляется независимо от остальной части выражения, поэтому совпавшая подстрока фиксируется до конца сопоставления, если только всё подвыражение не придётся отбросить и обработать заново.

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

Пример без атомарной группировки:

/".*"/.match('"Quote"') # => #<MatchData "\"Quote\"">

Разбор:

  1. Начальное подвыражение " в шаблоне соответствует первому символу " целевой строки.

  2. Следующее подвыражение .* соответствует следующей подстроке Quote" (включая завершающую двойную кавычку).

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

  4. Совпавшая подстрока откатывается на одну позицию: Quote.

  5. Теперь последнее подвыражение " соответствует последней подстроке ", и общее совпадение успешно.

Если подвыражение .* сгруппировано атомарно, возврат отключается, и общее совпадение завершается неудачно:

/"(?>.*)"/.match('"Quote"') # => nil

Атомарная группировка может влиять на производительность; см. Атомарная группа.

Вызовы подвыражений

Как видно выше, номер обратной ссылки (\n) или имя (\k<name>) дают доступ к захваченной подстроке; также можно обратиться к соответствующему подвыражению regexp — по номеру (\gn) или имени (\g<name>):

/\A(?<paren>\(\g<paren>*\))*\z/.match('(())')
# ^1
#      ^2
#           ^3
#                 ^4
#      ^5
#           ^6
#                      ^7
#                       ^8
#                       ^9
#                           ^10

Шаблон:

  1. Соответствует началу строки, то есть позиции перед первым символом.

  2. Входит в именованную группу paren.

  3. Соответствует первому символу строки, '('.

  4. Повторно вызывает группу paren, то есть возвращается ко второму шагу.

  5. Повторно входит в группу paren.

  6. Соответствует второму символу строки, '('.

  7. Пытается вызвать paren в третий раз, но безуспешно, поскольку это помешало бы успешному совпадению целиком.

  8. Соответствует третьему символу строки, ')'; отмечает конец второго рекурсивного вызова.

  9. Соответствует четвёртому символу строки, ')'.

  10. Соответствует концу строки.

См. раздел Вызовы подвыражений.

Условные конструкции

Условная конструкция имеет вид (?(cond)yes|no), где:

  • cond может быть номером или именем захвата.

  • Если захвачен cond, применяется вариант совпадения yes; в противном случае — вариант no.

  • Если он не нужен, |no можно опустить.

Примеры:

re = /\A(foo)?(?(1)(T)|(F))\z/
re.match('fooT') # => #<MatchData "fooT" 1:"foo" 2:"T" 3:nil>
re.match('F')    # => #<MatchData "F" 1:nil 2:nil 3:"F">
re.match('fooF') # => nil
re.match('T')    # => nil

re = /\A(?<xyzzy>foo)?(?(<xyzzy>)(T)|(F))\z/
re.match('fooT') # => #<MatchData "fooT" xyzzy:"foo">
re.match('F')    # => #<MatchData "F" xyzzy:nil>
re.match('fooF') # => nil
re.match('T')    # => nil

Оператор отсутствия

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

/(?~real)/.match('surrealist') # => #<MatchData "surrea">
/(?~real)ist/.match('surrealist') # => #<MatchData "ealist">
/sur(?~real)ist/.match('surrealist') # => nil

Юникод

Свойства Юникода

Конструкция /\p{property_name}/ (со строчной буквой p) сопоставляет символы по имени свойства Юникода, подобно классу символов; свойство Alpha задаёт алфавитные символы:

/\p{Alpha}/.match('a') # => #<MatchData "a">
/\p{Alpha}/.match('1') # => nil

Свойство можно инвертировать, поставив перед его именем знак вставки (^):

/\p{^Alpha}/.match('1') # => #<MatchData "1">
/\p{^Alpha}/.match('a') # => nil

Или с помощью \P (заглавная буква P):

/\P{Alpha}/.match('1') # => #<MatchData "1">
/\P{Alpha}/.match('a') # => nil

См. раздел Свойства Юникода, посвящённый regexp на основе многочисленных свойств.

Некоторые часто используемые свойства соответствуют выражениям в скобках POSIX:

  • /\p{Alnum}/: буквенно-цифровой символ

  • /\p{Alpha}/: алфавитный символ

  • /\p{Blank}/: пробел или табуляция

  • /\p{Cntrl}/: управляющий символ

  • /\p{Digit}/: цифры и аналогичные символы)

  • /\p{Lower}/: строчная буква

  • /\p{Print}/: как \p{Graph}, но включает пробел

  • /\p{Punct}/: знак препинания

  • /\p{Space}/: пробельный символ ([:blank:], символ новой строки, возврат каретки и т. д.)

  • /\p{Upper}/: заглавная буква

  • /\p{XDigit}/: цифра, допустимая в шестнадцатеричном числе (то есть 0–9a–fA–F)

Также часто используются:

  • /\p{Emoji}/: эмодзи Юникода.

  • /\p{Graph}/: символы, за исключением /\p{Cntrl}/ и /\p{Space}/. Обратите внимание, что сюда входят невидимые символы категории Юникода «Формат».

  • /\p{Word}/: элемент одной из перечисленных ниже категорий символов Юникода или символ, обладающий одним из указанных свойств Юникода:

    • Категории Юникода:

      • Mark (M).

      • Decimal Number (Nd)

      • Connector Punctuation (Pc).

    • Свойства Юникода:

      • Alpha

      • Join_Control

  • /\p{ASCII}/: символ из набора символов ASCII.

  • /\p{Any}/: любой символ Юникода (включая не назначенные символы).

  • /\p{Assigned}/: назначенный символ.

Категории символов Юникода

Имя категории символов Юникода:

  • Может указываться полностью или в сокращённой форме.

  • Не зависит от регистра.

  • Считает пробел, дефис и подчёркивание эквивалентными.

Примеры:

/\p{lu}/                # => /\p{lu}/
/\p{LU}/                # => /\p{LU}/
/\p{Uppercase Letter}/  # => /\p{Uppercase Letter}/
/\p{Uppercase_Letter}/  # => /\p{Uppercase_Letter}/
/\p{UPPERCASE-LETTER}/  # => /\p{UPPERCASE-LETTER}/

Ниже приведены сокращения и названия категорий символов Юникода. Списки символов каждой категории доступны по ссылкам.

Буквы:

  • L, Letter: LC, Lm или Lo.

  • LC, Cased_Letter: Ll, Lt или Lu.

  • Lu, строчная буква.

  • Lu, буква-модификатор.

  • Lu, прочая буква.

  • Lu, титульная буква.

  • Lu, заглавная буква.

Знаки:

  • M, Mark: Mc, Me или Mn.

  • Mc, Spacing_Mark.

  • Me, Enclosing_Mark.

  • Mn, Nonapacing_Mark.

Числа:

  • N, Number: Nd, Nl или No.

  • Nd, Decimal_Number.

  • Nl, Letter_Number.

  • No, Other_Number.

Пунктуация:

  • P, Punctuation: Pc, Pd, Pe, Pf, Pi, Po или Ps.

  • Pc, Connector_Punctuation.

  • Pd, Dash_Punctuation.

  • Pe, Close_Punctuation.

  • Pf, Final_Punctuation.

  • Pi, Initial_Punctuation.

  • Po, Other_Punctuation.

  • Ps, Open_Punctuation.

  • S, Symbol: Sc, Sk, Sm или So.

  • Sc, Currency_Symbol.

  • Sk, Modifier_Symbol.

  • Sm, Math_Symbol.

  • So, Other_Symbol.

  • Z, Separator: Zl, Zp или Zs.

  • Zl, Line_Separator.

  • Zp, Paragraph_Separator.

  • Zs, Space_Separator.

  • C, Other: Cc, Cf, Cn, Co или Cs.

  • Cc, Control.

  • Cf, Format.

  • Cn, Unassigned.

  • Co, Private_Use.

  • Cs, Surrogate.

Сценарии и блоки Unicode

К свойствам Unicode относятся:

  • сценарии Unicode; см. поддерживаемые сценарии.

  • блоки Unicode; см. поддерживаемые блоки.

Скобочные выражения POSIX

Скобочное выражение POSIX также похоже на класс символов. Эти выражения представляют собой переносимую альтернативу описанному выше и обладают дополнительным преимуществом: охватывают символы, не относящиеся к ASCII:

  • /\d/ соответствует только десятичным цифрам ASCII от 0 до 9.

  • /[[:digit:]]/ соответствует любому символу категории Unicode Decimal Number (Nd); см. ниже.

Скобочные выражения POSIX:

  • /[[:digit:]]/: соответствует цифре Unicode:

    /[[:digit:]]/.match('9')       # => #<MatchData "9">
    /[[:digit:]]/.match("\u1fbf9") # => #<MatchData "9">
    
  • /[[:xdigit:]]/: соответствует цифре, допустимой в шестнадцатеричном числе; эквивалентно [0-9a-fA-F].

  • /[[:upper:]]/: соответствует букве Unicode в верхнем регистре:

    /[[:upper:]]/.match('A')      # => #<MatchData "A">
    /[[:upper:]]/.match("\u00c6") # => #<MatchData "Æ">
    
  • /[[:lower:]]/: соответствует букве Unicode в нижнем регистре:

    /[[:lower:]]/.match('a')      # => #<MatchData "a">
    /[[:lower:]]/.match("\u01fd") # => #<MatchData "ǽ">
    
  • /[[:alpha:]]/: соответствует /[[:upper:]]/ или /[[:lower:]]/.

  • /[[:alnum:]]/: соответствует /[[:alpha:]]/ или /[[:digit:]]/.

  • /[[:space:]]/: соответствует символу пробела Unicode:

    /[[:space:]]/.match(' ')      # => #<MatchData " ">
    /[[:space:]]/.match("\u2005") # => #<MatchData " ">
    
  • /[[:blank:]]/: соответствует /[[:space:]]/ или символу табуляции:

    /[[:blank:]]/.match(' ')      # => #<MatchData " ">
    /[[:blank:]]/.match("\u2005") # => #<MatchData " ">
    /[[:blank:]]/.match("\t")     # => #<MatchData "\t">
    
  • /[[:cntrl:]]/: соответствует управляющему символу Unicode:

    /[[:cntrl:]]/.match("\u0000") # => #<MatchData "\u0000">
    /[[:cntrl:]]/.match("\u009f") # => #<MatchData "\u009F">
    
  • /[[:graph:]]/: соответствует любому символу, кроме /[[:space:]]/ или /[[:cntrl:]]/.

  • /[[:print:]]/: соответствует /[[:graph:]]/ или символу пробела.

  • /[[:punct:]]/: соответствует любому (символу пунктуации Unicode}[www.compart.com/en/unicode/category/Po]:

Ruby также поддерживает следующие скобочные выражения (не относящиеся к POSIX):

  • /[[:ascii:]]/: соответствует символу из набора символов ASCII.

  • /[[:word:]]/: соответствует символу одной из следующих категорий Unicode или с одним из следующих свойств Unicode:

    • Категории Unicode:

      • Mark (M).

      • Decimal Number (Nd)

      • Connector Punctuation (Pc).

    • Свойства Unicode:

      • Alpha

      • Join_Control

Комментарии

Комментарий можно включить в шаблон регулярного выражения с помощью конструкции (?#comment), где comment — подстрока, которую следует игнорировать. произвольный текст, игнорируемый механизмом регулярных выражений:

/foo(?#Ignore me)bar/.match('foobar') # => #<MatchData "foobar">

Комментарий не может содержать неэкранированный завершающий символ.

См. также Расширенный режим.

Режимы

Каждый из этих модификаторов задаёт режим для регулярного выражения:

  • i: /pattern/i включает режим без учёта регистра.

  • m: /pattern/m включает многострочный режим.

  • x: /pattern/x включает расширенный режим.

  • o: /pattern/o включает режим интерполяции.

Можно применить любые из этих модификаторов, все сразу или ни одного.

Модификаторы i, m и x можно применять к подвыражениям:

  • (?modifier) включает режим для последующих подвыражений

  • (?-modifier) отключает режим для последующих подвыражений

  • (?modifier:subexp) включает режим для subexp внутри группы

  • (?-modifier:subexp) отключает режим для subexp внутри группы

Пример:

re = /(?i)te(?-i)st/
re.match('test') # => #<MatchData "test">
re.match('TEst') # => #<MatchData "TEst">
re.match('TEST') # => nil
re.match('teST') # => nil

re = /t(?i:e)st/
re.match('test') # => #<MatchData "test">
re.match('tEst') # => #<MatchData "tEst">
re.match('tEST') # => nil

Метод Regexp#options возвращает целое число, значение которого показывает настройки режима без учёта регистра, многострочного режима и расширенного режима.

Режим без учёта регистра

По умолчанию регулярное выражение чувствительно к регистру:

/foo/.match('FOO')  # => nil

Модификатор i включает режим без учёта регистра:

/foo/i.match('FOO')
# => #<MatchData "FOO">

Метод Regexp#casefold? возвращает значение, указывающее, включён ли режим без учёта регистра.

Многострочный режим

Многострочный режим в Ruby — это то, что обычно называют «режимом dot-all»:

  • Без модификатора m подвыражение . не соответствует символам новой строки:

    /a.c/.match("a\nc")  # => nil
    
  • С модификатором оно соответствует и им:

    /a.c/m.match("a\nc") # => #<MatchData "a\nc">
    

В отличие от других языков, модификатор m не влияет на якоря ^ и $. В Ruby эти якоря всегда соответствуют границам строк.

Расширенный режим

Модификатор x включает расширенный режим, в котором:

  • Пробельные символы в шаблоне игнорируются.

  • Символ # обозначает остаток строки, в которой он находится, как комментарий, который также игнорируется при сопоставлении.

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

Regexp без расширенного режима (соответствует некоторым римским цифрам):

pattern = '^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$'
re = /#{pattern}/
re.match('MCMXLIII') # => #<MatchData "MCMXLIII" 1:"CM" 2:"XL" 3:"III">

Regexp в расширенном режиме:

pattern = <<-EOT
  ^                   # beginning of string
  M{0,3}              # thousands - 0 to 3 Ms
  (CM|CD|D?C{0,3})    # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 Cs),
                      #            or 500-800 (D, followed by 0 to 3 Cs)
  (XC|XL|L?X{0,3})    # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 Xs),
                      #        or 50-80 (L, followed by 0 to 3 Xs)
  (IX|IV|V?I{0,3})    # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 Is),
                      #        or 5-8 (V, followed by 0 to 3 Is)
  $                   # end of string
EOT
re = /#{pattern}/x
re.match('MCMXLIII') # => #<MatchData "MCMXLIII" 1:"CM" 2:"XL" 3:"III">

Режим интерполяции

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

Без модификатора o:

def letters; sleep 5; /[A-Z][a-z]/; end
words = %w[abc def xyz]
start = Time.now
words.each {|word| word.match(/\A[#{letters}]+\z/) }
Time.now - start # => 15.0174892

С модификатором o:

start = Time.now
words.each {|word| word.match(/\A[#{letters}]+\z/o) }
Time.now - start # => 5.0010866

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

Кодировки

По умолчанию регулярное выражение, содержащее только символы US-ASCII, имеет кодировку US-ASCII:

re = /foo/
re.source.encoding # => #<Encoding:US-ASCII>
re.encoding        # => #<Encoding:US-ASCII>

Считается, что регулярное выражение, содержащее символы, не относящиеся к US-ASCII, использует кодировку исходного текста. Это можно переопределить одним из следующих модификаторов.

  • /pat/n: US-ASCII, если содержит только символы US-ASCII, иначе ASCII-8BIT:

    /foo/n.encoding     # => #<Encoding:US-ASCII>
    /foo\xff/n.encoding # => #<Encoding:ASCII-8BIT>
    /foo\x7f/n.encoding # => #<Encoding:US-ASCII>
    
  • /pat/u: UTF-8

    /foo/u.encoding # => #<Encoding:UTF-8>
    
  • /pat/e: EUC-JP

    /foo/e.encoding # => #<Encoding:EUC-JP>
    
  • /pat/s: Windows-31J

    /foo/s.encoding # => #<Encoding:Windows-31J>
    

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

  • У них одинаковая кодировка.

  • Кодировка регулярного выражения фиксированная, а строка содержит только символы ASCII. Метод Regexp#fixed_encoding? возвращает значение, указывающее, имеет ли регулярное выражение фиксированную кодировку.

При попытке сопоставления несовместимых кодировок возникает исключение Encoding::CompatibilityError.

Пример:

re = eval("# encoding: ISO-8859-1\n/foo\\xff?/")
re.encoding                 # => #<Encoding:ISO-8859-1>
re =~ "foo".encode("UTF-8") # => 0
re =~ "foo\u0100"           # Raises Encoding::CompatibilityError

Кодировку можно явно зафиксировать, включив Regexp::FIXEDENCODING во второй аргумент метода Regexp.new:

# Regexp with encoding ISO-8859-1.
re = Regexp.new("a".force_encoding('iso-8859-1'), Regexp::FIXEDENCODING)
re.encoding  # => #<Encoding:ISO-8859-1>
# Target string with encoding UTF-8.
s = "a\u3042"
s.encoding   # => #<Encoding:UTF-8>
re.match(s)  # Raises Encoding::CompatibilityError.

Тайм-ауты

Если исходный текст регулярного выражения или целевая строка получены из ненадёжного источника, вредоносные значения могут привести к атаке типа «отказ в обслуживании»; чтобы предотвратить такую атаку, рекомендуется задать тайм-аут.

У Regexp есть два значения тайм-аута:

  • Тайм-аут класса по умолчанию, используемый для регулярного выражения, тайм-аут экземпляра которого равен nil; изначально он равен nil и может быть задан методом Regexp.timeout=:

    Regexp.timeout # => nil
    Regexp.timeout = 3.0
    Regexp.timeout # => 3.0
    
  • Тайм-аут экземпляра, который по умолчанию равен nil и может быть задан в Regexp.new:

    re = Regexp.new('foo', timeout: 5.0)
    re.timeout # => 5.0
    

Если regexp.timeout равен nil, тайм-аут «передаётся» в Regexp.timeout; если regexp.timeout не равен nil, именно это значение определяет тайм-аут:

| regexp.timeout Value | Regexp.timeout Value |            Result           |
|----------------------|----------------------|-----------------------------|
|         nil          |          nil         |       Never times out.      |
|         nil          |         Float        | Times out in Float seconds. |
|        Float         |          Any         | Times out in Float seconds. |

Оптимизация

Для некоторых значений шаблона и целевой строки время сопоставления может расти полиномиально или экспоненциально относительно размера входных данных; связанная с этим потенциальная уязвимость называется атакой типа «отказ в обслуживании с помощью регулярных выражений» (ReDoS).

Для предотвращения атак ReDoS при сопоставлении регулярных выражений может применяться оптимизация. При её применении время сопоставления растёт линейно (а не полиномиально или экспоненциально) относительно размера входных данных, поэтому атака ReDoS невозможна.

Эта оптимизация применяется, если шаблон соответствует следующим критериям:

  • Нет обратных ссылок.

  • Нет вызовов подвыражений.

  • Нет вложенных якорей просмотра назад или вперёд либо атомарных групп.

  • Нет вложенных квантификаторов с подсчётом (то есть вложенных квантификаторов вида {n}, {min,}, {,max} или {min,max})

С помощью метода Regexp.linear_time? можно определить, соответствует ли шаблон этим критериям:

Regexp.linear_time?(/a*/)     # => true
Regexp.linear_time?('a*')     # => true
Regexp.linear_time?(/(a*)\1/) # => false

Однако источник может быть ненадёжным, даже если метод возвращает true, поскольку в оптимизации используется мемоизация (которая может потребовать большого объёма памяти).

Ссылки

Рекомендуем прочитать:

  • Mastering Regular Expressions Джеффри Э. Ф. Фридла.

  • Regular Expressions Cookbook Яна Гойвертса и Стивена Левитана.

Изучите и протестируйте:

  • Rubular: интерактивный онлайн-редактор.

Константы

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

Публичные методы класса

compile (*args)

Псевдоним для Regexp.new

escape(string) → new_string Показать исходный код
static VALUE
rb_reg_s_quote(VALUE c, VALUE str)
{
    return rb_reg_quote(reg_operand(str, TRUE));
}

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

s = Regexp.escape('\*?{}.')      # => "\\\\\\*\\?\\{\\}\\."

Для любой строки s этот вызов возвращает объект MatchData:

r = Regexp.new(Regexp.escape(s)) # => /\\\\\\\*\\\?\\\{\\\}\\\./
r.match(s)                       # => #<MatchData "\\\\\\*\\?\\{\\}\\.">
json_create (object) Показать исходный код
# File ext/json/lib/json/add/regexp.rb, line 9
def self.json_create(object)
  new(object['s'], object['o'])
end

См. as_json.

last_match → matchdata or nil Показать исходный код
last_match(n) → string or nil
last_match(name) → string or nil
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();
}

Без аргумента возвращает значение $~, то есть результат последнего сопоставления с шаблоном (см. Глобальные переменные Regexp):

/c(.)t/ =~ 'cat'  # => 0
Regexp.last_match # => #<MatchData "cat" 1:"a">
/a/ =~ 'foo'      # => nil
Regexp.last_match # => nil

С неотрицательным целочисленным аргументом n возвращает _n_-е поле в matchdata, если оно есть, или nil, если его нет:

/c(.)t/ =~ 'cat'     # => 0
Regexp.last_match(0) # => "cat"
Regexp.last_match(1) # => "a"
Regexp.last_match(2) # => nil

С отрицательным целочисленным аргументом n отсчёт ведётся от последнего поля в обратном порядке:

Regexp.last_match(-1)       # => "a"

Со строковым аргументом или аргументом-символом name возвращает строковое значение именованной группы захвата, если она есть:

/(?<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"
Regexp.last_match('foo') # Raises IndexError.
linear_time?(re) Показать исходный код
linear_time?(string, options = 0)
static VALUE
rb_reg_s_linear_time_p(int argc, VALUE *argv, VALUE self)
{
    struct reg_init_args args;
    VALUE re = reg_extract_args(argc, argv, &args);

    if (NIL_P(re)) {
        re = reg_init_args(rb_reg_alloc(), args.str, args.enc, args.flags);
    }

    return RBOOL(onig_check_linear_time(RREGEXP_PTR(re)));
}

Возвращает true, если сопоставление с re может быть выполнено за линейное время относительно входной строки.

Regexp.linear_time?(/re/) # => true

Обратите внимание: это свойство интерпретатора Ruby, а не регулярного выражения, переданного в качестве аргумента. Одинаковые регулярные выражения могут выполняться за линейное время или нет в зависимости от используемого двоичного файла Ruby. Совместимость с предыдущими и будущими версиями для возвращаемого значения этого метода не гарантируется. Текущий алгоритм — (*1), но в будущем он может измениться. Другие реализации также могут вести себя иначе. Они могут всегда возвращать false для любого аргумента.

(*1): doi.org/10.1109/SP40001.2021.00032

new(string, options = 0, timeout: nil) → regexp Показать исходный код
new(regexp, timeout: nil) → regexp
static VALUE
rb_reg_initialize_m(int argc, VALUE *argv, VALUE self)
{
    struct reg_init_args args;
    VALUE re = reg_extract_args(argc, argv, &args);

    if (NIL_P(re)) {
        reg_init_args(self, args.str, args.enc, args.flags);
    }
    else {
        reg_copy(self, re);
    }

    set_timeout(&RREGEXP_PTR(self)->timelimit, args.timeout);

    return self;
}

Если указан аргумент string, возвращает новое регулярное выражение с заданной строкой и параметрами:

r = Regexp.new('foo') # => /foo/
r.source              # => "foo"
r.options             # => 0

Необязательный аргумент options может иметь одно из следующих значений:

  • String параметров:

    Regexp.new('foo', 'i')  # => /foo/i
    Regexp.new('foo', 'im') # => /foo/im
    
  • Побитовое ИЛИ одной или нескольких констант: Regexp::EXTENDED, Regexp::IGNORECASE, Regexp::MULTILINE и Regexp::NOENCODING:

    Regexp.new('foo', Regexp::IGNORECASE) # => /foo/i
    Regexp.new('foo', Regexp::EXTENDED)   # => /foo/x
    Regexp.new('foo', Regexp::MULTILINE)  # => /foo/m
    Regexp.new('foo', Regexp::NOENCODING)  # => /foo/n
    flags = Regexp::IGNORECASE | Regexp::EXTENDED |  Regexp::MULTILINE
    Regexp.new('foo', flags)              # => /foo/mix
    
  • nil или false; эти значения игнорируются.

  • Любое другое истинное значение; в этом случае регулярное выражение будет нечувствительным к регистру.

Если указан необязательный именованный аргумент timeout, его значение с плавающей точкой переопределяет интервал тайм-аута для класса, Regexp.timeout. Если в качестве +timeout передано nil, используется интервал тайм-аута для класса, Regexp.timeout.

Если указан аргумент regexp, возвращает новое регулярное выражение. Исходная строка, параметры и тайм-аут совпадают с regexp. Аргументы options и n_flag не действуют. Тайм-аут можно переопределить с помощью именованного аргумента timeout.

options = Regexp::MULTILINE
r = Regexp.new('foo', options, timeout: 1.1) # => /foo/m
r2 = Regexp.new(r)                           # => /foo/m
r2.timeout                                   # => 1.1
r3 = Regexp.new(r, timeout: 3.14)            # => /foo/m
r3.timeout                                   # => 3.14
escape(string) → new_string Показать исходный код
static VALUE
rb_reg_s_quote(VALUE c, VALUE str)
{
    return rb_reg_quote(reg_operand(str, TRUE));
}

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

s = Regexp.escape('\*?{}.')      # => "\\\\\\*\\?\\{\\}\\."

Для любой строки s этот вызов возвращает объект MatchData:

r = Regexp.new(Regexp.escape(s)) # => /\\\\\\\*\\\?\\\{\\\}\\\./
r.match(s)                       # => #<MatchData "\\\\\\*\\?\\{\\}\\.">
timeout → float or nil Показать исходный код
static VALUE
rb_reg_s_timeout_get(VALUE dummy)
{
    double d = hrtime2double(rb_reg_match_time_limit);
    if (d == 0.0) return Qnil;
    return DBL2NUM(d);
}

Возвращает текущий интервал тайм-аута по умолчанию для сопоставления с Regexp в секундах. nil означает, что тайм-аут по умолчанию не настроен.

timeout = float or nil Показать исходный код
static VALUE
rb_reg_s_timeout_set(VALUE dummy, VALUE timeout)
{
    rb_ractor_ensure_main_ractor("can not access Regexp.timeout from non-main Ractors");

    set_timeout(&rb_reg_match_time_limit, timeout);

    return timeout;
}

Задаёт интервал тайм-аута по умолчанию для сопоставления с Regexp в секундах. nil означает, что тайм-аут по умолчанию не настроен. Эта настройка действует глобально для процесса. Чтобы задать тайм-аут для каждого объекта Regexp, используйте именованный аргумент timeout для Regexp.new.

Regexp.timeout = 1
/^a*b?a*$/ =~ "a" * 100000 + "x" #=> regexp match timeout (RuntimeError)
try_convert(object) → regexp or nil Показать исходный код
static VALUE
rb_reg_s_try_convert(VALUE dummy, VALUE re)
{
    return rb_check_regexp_type(re);
}

Возвращает object, если это регулярное выражение:

Regexp.try_convert(/re/) # => /re/

В противном случае, если object отвечает на :to_regexp, вызывает object.to_regexp и возвращает результат.

Возвращает nil, если object не отвечает на :to_regexp.

Regexp.try_convert('re') # => nil

Вызывает исключение, если object.to_regexp не возвращает регулярное выражение.

union(*patterns) → regexp Показать исходный код
union(array_of_patterns) → regexp
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);
}

Возвращает новое регулярное выражение, представляющее объединение заданных шаблонов:

r = Regexp.union(%w[cat dog])      # => /cat|dog/
r.match('cat')      # => #<MatchData "cat">
r.match('dog')      # => #<MatchData "dog">
r.match('cog')      # => nil

Для каждого строкового шаблона используется Regexp.new(pattern):

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(/foo/i, /bar/m, /baz/x)
# => /(?i-mx:foo)|(?m-ix:bar)|(?x-mi:baz)/
Regexp.union([/foo/i, /bar/m, /baz/x])
# => /(?i-mx:foo)|(?m-ix:bar)|(?x-mi:baz)/

Если аргументы не указаны, возвращает /(?!)/:

Regexp.union # => /(?!)/

Если какой-либо шаблон регулярного выражения содержит группы захвата, поведение не определено.

Публичные методы экземпляра

regexp == object → true or false

Возвращает true, если object — другое регулярное выражение Regexp с теми же шаблоном, флагами и кодировкой, что и у self; в противном случае возвращает false:

/foo/ == Regexp.new('foo')                          # => true
/foo/ == /foo/i                                     # => false
/foo/ == Regexp.new('food')                         # => false
/foo/ == Regexp.new("abc".force_encoding("euc-jp")) # => false
Псевдоним для: eql?
regexp === string → true or false Показать исходный код
static 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);
    return RBOOL(start >= 0);
}

Возвращает true, если self находит совпадение в string:

/^[a-z]*$/ === 'HELLO' # => false
/^[A-Z]*$/ === 'HELLO' # => true

Этот метод вызывается в выражениях case:

s = 'HELLO'
case s
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"
regexp =~ string → integer or nil Показать исходный код
VALUE
rb_reg_match(VALUE re, VALUE str)
{
    long pos = reg_match_pos(re, &str, 0, NULL);
    if (pos < 0) return Qnil;
    pos = rb_str_sublen(str, pos);
    return LONG2FIX(pos);
}

Возвращает целочисленный индекс (в символах) первого совпадения для self и string или nil, если совпадений нет; также устанавливает глобальные переменные Regexp:

/at/ =~ 'input data' # => 7
$~                   # => #<MatchData "at">
/ax/ =~ 'input data' # => nil
$~                   # => nil

Присваивает именованные группы захвата локальным переменным с такими же именами только в том случае, если self:

  • Является литералом регулярного выражения; см. Литералы регулярных выражений.

  • Не содержит интерполяций; см. Интерполяция регулярных выражений.

  • Находится в левой части выражения.

Пример:

/(?<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

Не выполняет присваивание локальных переменных, если self не является литералом регулярного выражения:

r = /(?<foo>\w+)\s*=\s*(?<foo>\w+)/
r =~ '  x = y  '
p foo # Undefined local variable
p bar # Undefined local variable

Присваивание не происходит, если регулярное выражение находится не слева:

'  x = y  ' =~ /(?<foo>\w+)\s*=\s*(?<foo>\w+)/
p foo, foo # Undefined local variables

Интерполяция регулярного выражения, #{}, также отключает присваивание:

r = /(?<foo>\w+)/
/(?<foo>\w+)\s*=\s*#{r}/ =~ 'x = y'
p foo # Undefined local variable
~ rxp → integer or nil Показать исходный код
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 =~ $_:

$_ = "input data"
~ /at/ # => 7
as_json (*) Показать исходный код
# File ext/json/lib/json/add/regexp.rb, line 28
def as_json(*)
  {
    JSON.create_id => self.class.name,
    'o'            => options,
    's'            => source,
  }
end

Методы Regexp#as_json и Regexp.json_create можно использовать для сериализации и десериализации объекта Regexp; см. Marshal.

Метод Regexp#as_json сериализует self, возвращая хеш из двух элементов, представляющий self:

require 'json/add/regexp'
x = /foo/.as_json
# => {"json_class"=>"Regexp", "o"=>0, "s"=>"foo"}

Метод JSON.create десериализует такой хеш и возвращает объект Regexp:

Regexp.json_create(x) # => /foo/
casefold?→ true or false Показать исходный код
static VALUE
rb_reg_casefold_p(VALUE re)
{
    rb_reg_check(re);
    return RBOOL(RREGEXP_PTR(re)->options & ONIG_OPTION_IGNORECASE);
}

Возвращает true, если в self установлен флаг нечувствительности к регистру; в противном случае возвращает false:

/a/.casefold?           # => false
/a/i.casefold?          # => true
/(?i:a)/.casefold?      # => false
encoding → encoding Показать исходный код
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, представляющий кодировку self; см. Кодировки.

Связанный раздел: см. Запросы.

eql? Показать исходный код
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;
    return RBOOL(memcmp(RREGEXP_SRC_PTR(re1), RREGEXP_SRC_PTR(re2), RREGEXP_SRC_LEN(re1)) == 0);
}
Также имеет псевдоним: ==
fixed_encoding? → true or false Показать исходный код
static VALUE
rb_reg_fixed_encoding_p(VALUE re)
{
    return RBOOL(FL_TEST(re, KCODE_FIXED));
}

Возвращает false, если self применимо к строке с любой ASCII-совместимой кодировкой; в противном случае возвращает true:

r = /a/                                          # => /a/
r.fixed_encoding?                               # => false
r.match?("\u{6666} a")                          # => true
r.match?("\xa1\xa2 a".force_encoding("euc-jp")) # => true
r.match?("abc".force_encoding("euc-jp"))        # => true

r = /a/u                                        # => /a/
r.fixed_encoding?                               # => true
r.match?("\u{6666} a")                          # => true
r.match?("\xa1\xa2".force_encoding("euc-jp"))   # Raises exception.
r.match?("abc".force_encoding("euc-jp"))        # => true

r = /\u{6666}/                                  # => /\u{6666}/
r.fixed_encoding?                               # => true
r.encoding                                      # => #<Encoding:UTF-8>
r.match?("\u{6666} a")                          # => true
r.match?("\xa1\xa2".force_encoding("euc-jp"))   # Raises exception.
r.match?("abc".force_encoding("euc-jp"))        # => false
hash → integer Показать исходный код
VALUE
rb_reg_hash(VALUE re)
{
    st_index_t hashval = reg_hash(re);
    return ST2FIX(hashval);
}

Возвращает целочисленное хеш-значение для self.

Связанный метод: Object#hash.

inspect → string Показать исходный код
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(re);
}

Возвращает отформатированное строковое представление self:

/ab+c/ix.inspect # => "/ab+c/ix"

Связанный метод: Regexp#to_s.

match(string, offset = 0) → matchdata or nil Показать исходный код
match(string, offset = 0) {|matchdata| ... } → object
static VALUE
rb_reg_match_m(int argc, VALUE *argv, VALUE re)
{
    VALUE result = Qnil, 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, &result);
    if (pos < 0) {
        rb_backref_set(Qnil);
        return Qnil;
    }
    rb_match_busy(result);
    if (!NIL_P(result) && rb_block_given_p()) {
        return rb_yield(result);
    }
    return result;
}

Если блок не задан, возвращает объект MatchData, описывающий совпадение, если оно есть, или nil, если его нет; поиск начинается с заданной позиции offset в символах строки string:

/abra/.match('abracadabra')      # => #<MatchData "abra">
/abra/.match('abracadabra', 4)   # => #<MatchData "abra">
/abra/.match('abracadabra', 8)   # => nil
/abra/.match('abracadabra', 800) # => nil

string = "\u{5d0 5d1 5e8 5d0}cadabra"
/abra/.match(string, 7)          #=> #<MatchData "abra">
/abra/.match(string, 8)          #=> nil
/abra/.match(string.b, 8)        #=> #<MatchData "abra">

Если задан блок, он вызывается только при обнаружении совпадения; возвращается значение блока:

/abra/.match('abracadabra') {|matchdata| p matchdata }
# => #<MatchData "abra">
/abra/.match('abracadabra', 4) {|matchdata| p matchdata }
# => #<MatchData "abra">
/abra/.match('abracadabra', 8) {|matchdata| p matchdata }
# => nil
/abra/.match('abracadabra', 8) {|marchdata| fail 'Cannot happen' }
# => nil

Вывод (из первых двух блоков выше):

#<MatchData "abra">
#<MatchData "abra">

 /(.)(.)(.)/.match("abc")[2] # => "b"
 /(.)(.)/.match("abc", 1)[2] # => "c"
match?(string) → true or false Показать исходный код
match?(string, offset = 0) → true or false
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
named_captures → hash Показать исходный код
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;
}

Возвращает хеш с именованными группами захвата self (см. Именованные группы захвата):

  • Каждый ключ — это имя именованной группы захвата.

  • Каждое значение — это массив целочисленных индексов для соответствующей именованной группы захвата.

Примеры:

/(?<foo>.)(?<bar>.)/.named_captures # => {"foo"=>[1], "bar"=>[2]}
/(?<foo>.)(?<foo>.)/.named_captures # => {"foo"=>[1, 2]}
/(.)(.)/.named_captures             # => {}
names → array_of_names Показать исходный код
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                      # => []
options → integer Показать исходный код
static VALUE
rb_reg_options_m(VALUE re)
{
    int options = rb_reg_options(re);
    return INT2NUM(options);
}

Возвращает целое число, биты которого указывают, какие параметры установлены в self.

Биты параметров:

Regexp::IGNORECASE # => 1
Regexp::EXTENDED   # => 2
Regexp::MULTILINE  # => 4

Примеры:

/foo/.options    # => 0
/foo/i.options   # => 1
/foo/x.options   # => 2
/foo/m.options   # => 4
/foo/mix.options # => 7

Обратите внимание: в возвращаемом целом числе могут быть установлены дополнительные биты; они хранятся внутри self, игнорируются при передаче в Regexp.new и могут игнорироваться вызывающим кодом:

Возвращает набор битов, соответствующих параметрам, использованным при создании этого регулярного выражения (подробности см. в Regexp::new). Обратите внимание: в возвращаемых параметрах могут присутствовать дополнительные биты, которые используются внутри кода регулярных выражений. Эти дополнительные биты игнорируются, если параметры передаются в Regexp::new:

r = /\xa1\xa2/e                 # => /\xa1\xa2/
r.source                        # => "\\xa1\\xa2"
r.options                       # => 16
Regexp.new(r.source, r.options) # => /\xa1\xa2/
source → string Показать исходный код
static VALUE
rb_reg_source(VALUE re)
{
    VALUE str;

    rb_reg_check(re);
    str = rb_str_dup(RREGEXP_SRC(re));
    return str;
}

Возвращает исходную строку self:

/ab+c/ix.source # => "ab+c"

Экранирующие последовательности Regexp сохраняются:

/\x20\+/.source  # => "\\x20\\+"

Экранирующие символы лексера не сохраняются:

/\//.source  # => "/"
timeout → float or nil Показать исходный код
static VALUE
rb_reg_timeout_get(VALUE re)
{
    rb_reg_check(re);
    double d = hrtime2double(RREGEXP_PTR(re)->timelimit);
    if (d == 0.0) return Qnil;
    return DBL2NUM(d);
}

Возвращает интервал тайм-аута для сопоставления с Regexp в секундах. nil означает, что тайм-аут по умолчанию не настроен.

Эта настройка применяется к отдельному объекту. Глобальная настройка, заданная методом Regexp.timeout=, игнорируется, если задана настройка для отдельного объекта.

re = Regexp.new("^a*b?a*$", timeout: 1)
re.timeout               #=> 1.0
re =~ "a" * 100000 + "x" #=> regexp match timeout (RuntimeError)
to_json (*args) Показать исходный код
# File ext/json/lib/json/add/regexp.rb, line 45
def to_json(*args)
  as_json.to_json(*args)
end

Возвращает строку JSON, представляющую self:

require 'json/add/regexp'
puts /foo/.to_json

Вывод:

{"json_class":"Regexp","o":0,"s":"foo"}
to_s → string Показать исходный код
static VALUE
rb_reg_to_s(VALUE re)
{
    return rb_reg_str_with_term(re, '/');
}

Возвращает строку, содержащую параметры и строку self:

r0 = /ab+c/ix
s0 = r0.to_s # => "(?ix-m:ab+c)"

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

r1 = Regexp.new(s0) # => /(?ix-m:ab+c)/
r2 = /#{s0}/        # => /(?ix-m:ab+c)/

Обратите внимание: r1 и r2 не равны r0, поскольку их исходные строки различаются:

r0 == r1  # => false
r0.source # => "ab+c"
r1.source # => "(?ix-m:ab+c)"

Связанный метод: Regexp#inspect.

Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API