类 Regexp

正则表达式(也称为regexp)是一个匹配模式(也简称为模式)。

正则表达式的常用表示法是使用括住的斜杠字符

/foo/

正则表达式可以应用于目标字符串;字符串中与模式匹配的部分(如果有)称为匹配,并且可以说匹配

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.

正则表达式的用途

可以使用正则表达式

  • 根据给定的模式提取子字符串

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

    请参阅 方法 match运算符 =~ 部分。

  • 确定字符串是否与给定的模式匹配

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

    请参阅 方法 match? 部分。

  • 作为对其他类和模块中某些方法的调用参数;大多数此类方法接受的参数可以是字符串或(功能更强大的)正则表达式。

    请参阅正则表达式方法

正则表达式对象

一个正则表达式对象具有

  • 一个源;请参阅 来源

  • 几个模式;请参阅 模式

  • 一个超时;请参阅 超时

  • 一个编码;请参阅 编码

创建正则表达式

可以使用以下方式创建正则表达式

  • 使用斜杠字符的正则表达式字面量(请参阅正则表达式字面量

    # This is a very common usage.
    /foo/ # => /foo/
    
  • 一个 %r 正则表达式字面量(请参阅%r:正则表达式字面量

    # 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#matchString#matchSymbol#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

全局变量

某些面向正则表达式的方法将值分配给全局变量

受影响的全局变量是

  • $~:返回一个 MatchData 对象,或 nil

  • $&:返回字符串的匹配部分,或 nil

  • $`:返回字符串中匹配项左侧的部分,或 nil

  • $':返回字符串中匹配项右侧的部分,或 nil

  • $+:返回最后匹配的组,或 nil

  • $1$2 等:返回第一个、第二个等匹配的组,或 nil。请注意,$0 截然不同;它返回当前正在执行的程序的名称。

示例

# 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? 不设置全局变量。

来源

如上所示,最简单的正则表达式使用字面表达式作为其源

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

丰富的可用子表达式集合赋予正则表达式强大的功能和灵活性

特殊字符

正则表达式特殊字符,称为元字符,在某些上下文中具有特殊含义;根据上下文,这些有时是元字符

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

要按字面意思匹配元字符,请使用反斜杠转义它

# 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#gsubString#scan 之类的方法中,它在每次迭代时都会发生变化。它最初匹配主体的开头,并且在随后的每次迭代中,它都匹配上次匹配结束的位置。

    "    a b c".gsub(/ /, '_')   # => "____a_b_c"
    "    a b c".gsub(/\G /, '_') # => "____a b c"
    

    在诸如 Regexp#matchString#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">

匹配重置锚点

  • \K:匹配重置:正则表达式中 \K 之前匹配的内容将从结果中排除。例如,以下两个正则表达式几乎等效

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

    它们匹配相同的字符串,并且 $& 等于 'c',而匹配的位置不同。

    以下两个正则表达式也是如此

    /(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 提供了一种构建带有选择的正则表达式的便捷方法。

量词

一个简单的正则表达式匹配一个字符

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

添加的量词指定需要或允许匹配多少次

  • * - 匹配零次或多次

    /\w*/.match('')
    # => #<MatchData "">
    /\w*/.match('x')
    # => #<MatchData "x">
    /\w*/.match('xyz')
    # => #<MatchData "yz">
    
  • + - 匹配一次或多次

    /\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} 及其变体支持占有匹配。

更多

分组和捕获

一个简单的正则表达式(最多)有一个匹配项

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

添加一个或多个括号对 (子表达式),定义了,这可能会导致多个匹配的子字符串,称为捕获

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">

反向引用

组匹配也可以在正则表达式本身中引用;这种引用称为反向引用

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

此表显示了上面正则表达式中的每个子表达式如何匹配目标字符串中的子字符串

| 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'          |

一个正则表达式可以包含任意数量的组

  • 对于大量的组

    • 普通的 \n 表示法仅适用于范围 (1..9) 内的 n

    • MatchData[n] 表示法适用于任何非负数 n

  • \0 是一个特殊的反向引用,指的是整个匹配的字符串;它不能在正则表达式本身中使用,但可以在外部使用(例如,在替换方法调用中)

    '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"

当正则表达式包含命名捕获时,没有未命名的捕获

/\$(?<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">

当(且仅当)正则表达式包含命名捕获组并出现在 =~ 运算符之前时,捕获的子字符串将分配给具有相应名称的局部变量

/\$(?<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>) 可以访问捕获的子字符串;还可以通过编号 (\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

Unicode

Unicode 属性

/\p{property_name}/ 构造(使用小写 p)使用 Unicode 属性名称匹配字符,很像字符类;属性 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

请参阅 Unicode 属性,了解基于众多属性的正则表达式。

一些常用的属性对应于 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}/:Unicode 表情符号。

  • /\p{Graph}/:排除 /\p{Cntrl}//\p{Space}/ 的字符。请注意,Unicode {“Format”}[https://www.compart.com/en/unicode/category/Cf] 类别下的不可见字符也包括在内。

  • /\p{Word}/:这些 Unicode 字符类别之一的成员(请参见下文)或具有这些 Unicode 属性之一的字符

    • Unicode 类别

      • 标记 (M)。

      • 十进制数字 (Nd)

      • 连接符标点 (Pc)。

    • Unicode 属性

      • Alpha

      • Join_Control

  • /\p{ASCII}/:ASCII 字符集中的字符。

  • /\p{Any}/:任何 Unicode 字符(包括未分配的字符)。

  • /\p{Assigned}/:已分配的字符。

Unicode 字符类别

Unicode 字符类别名称

  • 可以是其全名或缩写名。

  • 不区分大小写。

  • 将空格、连字符和下划线视为等效。

示例

/\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}/

以下是 Unicode 字符类别缩写和名称。每个类别中的字符枚举在链接中。

字母

标记

数字

标点符号

Unicode 脚本和区块

Unicode 属性包括

POSIX 括号表达式

POSIX *括号表达式* 也类似于字符类。这些表达式为上述内容提供了一种可移植的替代方案,并具有包含非 ASCII 字符的额外好处。

  • /\d/ 仅匹配 ASCII 十进制数字 09

  • /[[:digit:]]/ 匹配 Unicode 十进制数字 (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 类别

      • 标记 (M)。

      • 十进制数字 (Nd)

      • 连接符标点 (Pc)。

    • Unicode 属性

      • Alpha

      • Join_Control

注释

可以使用 (?#comment) 结构在正则表达式模式中包含注释,其中 *comment* 是一个要忽略的子字符串。正则表达式引擎忽略的任意文本

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

注释不能包含未转义的终止符字符。

另请参阅 扩展模式

模式

以下每个修饰符都为正则表达式设置一个模式

可以应用其中任何、所有或没有。

修饰符 imx 可以应用于子表达式

  • (?*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 中的多行模式通常称为“点全部模式”

  • 如果没有 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 字符。Method 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.new 的第二个参数中包含 Regexp::FIXEDENCODING 来显式固定编码

# 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,来自不受信任的来源也可能不安全,因为优化使用了记忆化(这可能会导致大量内存消耗)。

参考资料

阅读(在线 PDF 书籍)

探索,测试(交互式在线编辑器)

常量

EXTENDED

请参阅 Regexp.optionsRegexp.new

FIXEDENCODING

请参阅 Regexp.optionsRegexp.new

IGNORECASE

请参阅 Regexp.optionsRegexp.new

MULTILINE

请参阅 Regexp.optionsRegexp.new

NOENCODING

请参阅 Regexp.optionsRegexp.new

公共类方法

compile(*args)

Regexp.new 的别名

escape(string) → new_string 点击以切换源代码

返回一个新字符串,其中转义了正则表达式中具有特殊含义的任何字符

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

对于任何字符串 s,此调用返回一个 MatchData 对象

r = Regexp.new(Regexp.escape(s)) # => /\\\\\\\*\\\?\\\{\\\}\\\./
r.match(s)                       # => #<MatchData "\\\\\\*\\?\\{\\}\\.">
static VALUE
rb_reg_s_quote(VALUE c, VALUE str)
{
    return rb_reg_quote(reg_operand(str, TRUE));
}
last_match → matchdata or nil 点击以切换源代码
last_match(n) → string or nil
last_match(name) → string or nil

如果没有参数,则返回 $~ 的值,这是最近一次模式匹配的结果(请参阅 Regexp 全局变量

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

如果带有非负整数参数 n,则返回匹配数据中的第 _n_ 个字段(如果有),如果没有则返回 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.
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();
}
linear_time?(re) 点击以切换源代码
linear_time?(string, options = 0)

如果对 re 的匹配可以在输入字符串的线性时间内完成,则返回 true

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

请注意,这是 ruby 解释器的一个属性,而不是参数正则表达式的属性。相同的正则表达式是否可以在线性时间内运行取决于您的 ruby 二进制文件。对于此方法的返回值,不保证向前或向后的兼容性。我们当前的算法是 (*1),但这在未来可能会发生变化。替代实现的行为也可能不同。它们可能始终对所有内容返回 false。

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

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)));
}
new(string, options = 0, timeout: nil) → regexp 点击以切换源代码
new(regexp, timeout: nil) → regexp

如果给定参数 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::EXTENDEDRegexp::IGNORECASERegexp::MULTILINERegexp::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
    
  • nilfalse,将被忽略。

  • 任何其他真值,在这种情况下,正则表达式将不区分大小写。

如果给定可选的关键字参数 timeout,则其浮点值将覆盖类的超时时间间隔 Regexp.timeout。如果将 nil 作为 +timeout 传递,则它将使用类的超时时间间隔 Regexp.timeout

如果给定参数 regexp,则返回一个新的正则表达式。源、选项、超时时间与 regexp 相同。optionsn_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
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;
}
escape(string) → new_string 点击以切换源代码

返回一个新字符串,其中转义了正则表达式中具有特殊含义的任何字符

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

对于任何字符串 s,此调用返回一个 MatchData 对象

r = Regexp.new(Regexp.escape(s)) # => /\\\\\\\*\\\?\\\{\\\}\\\./
r.match(s)                       # => #<MatchData "\\\\\\*\\?\\{\\}\\.">
static VALUE
rb_reg_s_quote(VALUE c, VALUE str)
{
    return rb_reg_quote(reg_operand(str, TRUE));
}
timeout → float or nil 点击以切换源代码

它返回 Regexp 匹配的当前默认超时时间间隔(以秒为单位)。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);
}
timeout = float or nil 点击以切换源代码

它设置 Regexp 匹配的默认超时时间间隔(以秒为单位)。nil 表示没有默认超时配置。此配置是进程全局的。如果要为每个 Regexp 设置超时时间,请使用 Regexp.newtimeout 关键字。

Regexp.timeout = 1
/^a*b?a*$/ =~ "a" * 100000 + "x" #=> regexp match timeout (RuntimeError)
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;
}
try_convert(object) → regexp or nil 点击以切换源代码

如果 object 是正则表达式,则返回 object

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

否则,如果 object 响应 :to_regexp,则调用 object.to_regexp 并返回结果。

如果 object 不响应 :to_regexp,则返回 nil

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

除非 object.to_regexp 返回正则表达式,否则引发异常。

static VALUE
rb_reg_s_try_convert(VALUE dummy, VALUE re)
{
    return rb_check_regexp_type(re);
}
union(*patterns) → regexp 点击以切换源代码
union(array_of_patterns) → regexp

返回一个新正则表达式,它是给定模式的并集

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 # => /(?!)/

如果任何正则表达式模式包含捕获,则行为未指定。

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 == object → true or false

如果 object 是另一个正则表达式,其模式、标志和编码与 self 相同,则返回 true,否则返回 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 点击以切换源代码

如果 selfstring 中找到匹配项,则返回 true

/^[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"
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);
}
regexp =~ string → integer or nil 点击以切换源代码

返回 selfstring 的第一个匹配项的整数索引(以字符为单位),如果没有则返回 nil;还会设置 rdoc-ref: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
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);
}
casefold?→ true or false 点击以切换源代码

如果 self 中设置了不区分大小写标志,则返回 true,否则返回 false

/a/.casefold?           # => false
/a/i.casefold?          # => true
/(?i:a)/.casefold?      # => false
static VALUE
rb_reg_casefold_p(VALUE re)
{
    rb_reg_check(re);
    return RBOOL(RREGEXP_PTR(re)->options & ONIG_OPTION_IGNORECASE);
}
encoding → encoding 点击以切换源代码

返回表示 obj 编码的 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);
}
eql? == object -> true or false 点击以切换源代码

如果 object 是另一个正则表达式,其模式、标志和编码与 self 相同,则返回 true,否则返回 false

/foo/ == Regexp.new('foo')                          # => true
/foo/ == /foo/i                                     # => false
/foo/ == Regexp.new('food')                         # => false
/foo/ == Regexp.new("abc".force_encoding("euc-jp")) # => false
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 点击以切换源代码

如果 self 适用于具有任何 ASCII 兼容编码的字符串,则返回 false;否则返回 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
static VALUE
rb_reg_fixed_encoding_p(VALUE re)
{
    return RBOOL(FL_TEST(re, KCODE_FIXED));
}
hash → integer 点击以切换源代码

返回 self 的整数哈希值。

相关:Object#hash

VALUE
rb_reg_hash(VALUE re)
{
    st_index_t hashval = reg_hash(re);
    return ST2FIX(hashval);
}
inspect → string 点击以切换源代码

返回 self 的格式良好的字符串表示形式

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

相关:Regexp#to_s

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);
}
match(string, offset = 0) → matchdata or nil 点击以切换源代码
match(string, offset = 0) {|matchdata| ... } → object

如果没有给定块,则返回描述匹配项的 MatchData 对象(如果有),如果没有则返回 nil;搜索从 string 中给定的字符 offset 开始

/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"
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;
}
match?(string) → true or false 点击以切换源代码
match?(string, offset = 0) → true or false

返回 truefalse,以指示正则表达式是否匹配,而不会更新 $~ 和其他相关变量。如果存在第二个参数,则它指定字符串中开始搜索的位置。

/R.../.match?("Ruby")    # => true
/R.../.match?("Ruby", 1) # => false
/P.../.match?("Ruby")    # => false
$&                       # => nil
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);
}
named_captures → hash 点击以切换源代码

返回一个哈希,表示 self 的命名捕获(请参阅 命名捕获

  • 每个键都是命名捕获的名称。

  • 每个值都是该命名捕获的整数索引数组。

示例

/(?<foo>.)(?<bar>.)/.named_captures # => {"foo"=>[1], "bar"=>[2]}
/(?<foo>.)(?<foo>.)/.named_captures # => {"foo"=>[1, 2]}
/(.)(.)/.named_captures             # => {}
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;
}
names → array_of_names 点击以切换源代码

返回捕获的名称数组(请参阅 命名捕获

/(?<foo>.)(?<bar>.)(?<baz>.)/.names # => ["foo", "bar", "baz"]
/(?<foo>.)(?<foo>.)/.names          # => ["foo"]
/(.)(.)/.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;
}
options → integer 点击以切换源代码

返回一个整数,其位显示 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/
static VALUE
rb_reg_options_m(VALUE re)
{
    int options = rb_reg_options(re);
    return INT2NUM(options);
}
source → string 点击切换源

返回self的原始字符串

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

Regexp 转义序列被保留

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

词法分析器转义字符不被保留

/\//.source  # => "/"
static VALUE
rb_reg_source(VALUE re)
{
    VALUE str;

    rb_reg_check(re);
    str = rb_str_dup(RREGEXP_SRC(re));
    return str;
}
timeout → float or nil 点击以切换源代码

它返回 Regexp 匹配的超时时间间隔,以秒为单位。nil 表示没有默认超时配置。

此配置是每个对象独立的。如果设置了每个对象的配置,则会忽略由 Regexp.timeout= 设置的全局配置。

re = Regexp.new("^a*b?a*$", timeout: 1)
re.timeout               #=> 1.0
re =~ "a" * 100000 + "x" #=> regexp match timeout (RuntimeError)
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);
}
to_s → string 点击切换源

返回一个显示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)/

请注意,r1r2 不等于 r0,因为它们的原始字符串不同

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

相关链接: Regexp#inspect

static VALUE
rb_reg_to_s(VALUE re)
{
    return rb_reg_str_with_term(re, '/');
}
~ rxp → integer or nil 点击切换源

等效于 rxp =~ $_

$_ = "input data"
~ /at/ # => 7
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);
}