本文简要介绍ruby语言中 Regexp类 的用法。
Regexp 包含一个正则表达式,用于将模式与字符串匹配。正则表达式是使用 /.../ 和 %r{...} 文字以及由 Regexp::new 构造函数创建的。
您可以使用以下命令显式创建 Regexp 对象:
正则表达式(regexp s)是说明字符串内容的模式。它们用于测试字符串是否包含给定模式,或提取匹配的部分。它们是使用 / pat / 和 %r{ pat } 文字或 Regexp.new 构造函数创建的。
正则表达式通常用正斜杠 (/) 分隔。例如:
/hay/ =~ 'haystack' #=> 0
/y/.match('haystack') #=> #<MatchData "y">
如果一个字符串包含它被称为 match 的模式。文字字符串匹配自身。
这里‘haystack’ 不包含模式‘needle’,因此不匹配:
/needle/.match('haystack') #=> nil
这里‘haystack’ 包含模式‘hay’,所以它匹配:
/hay/.match('haystack') #=> #<MatchData "hay">
具体来说,/st/ 要求字符串包含字母 s 后跟字母 t ,因此它也匹配 haystack 。
=~ 和 Regexp#match
模式匹配可以通过使用=~ 运算符或 Regexp#match 方法来实现。
=~ 运算符
=~ 是 Ruby 的基本 pattern-matching 运算符。当一个操作数是正则表达式而另一个是字符串时,则正则表达式用作匹配字符串的模式。 (此运算符由 Regexp 和 String 等效定义,因此 String 和 Regexp 的顺序无关紧要。其他类可能有不同的 =~ 实现。)如果找到匹配项,则运算符返回字符串中第一个匹配的索引,否则返回 nil 。
/hay/ =~ 'haystack' #=> 0
'haystack' =~ /hay/ #=> 0
/a/ =~ 'haystack' #=> 1
/u/ =~ 'haystack' #=> nil
使用带有 String 和 Regexp 的=~ 运算符,在成功匹配后设置$~ 全局变量。 $~ 包含一个 MatchData 对象。 Regexp.last_match 等同于 $~ 。
Regexp#match 方法
/st/.match('haystack') #=> #<MatchData "st">
元字符和转义符
以下是 metacharacters ( , ) , [ , ] , { , } , . , ? , + , * 。当它们出现在一个模式中时,它们具有特定的含义。要从字面上匹配它们,它们必须是backslash-escaped。要从字面上匹配反斜杠,backslash-escape 它:\\。
/1 \+ 2 = 3\?/.match('Does 1 + 2 = 3?') #=> #<MatchData "1 + 2 = 3?">
/a\\\\b/.match('a\\\\b') #=> #<MatchData "a\\b">
模式的行为类似于双引号字符串,并且可以包含相同的反斜杠转义符(\s 的含义不同,但是,请参见下文)。
/\s\u{6771 4eac 90fd}/.match("Go to 東京都")
#=> #<MatchData " 東京都">
可以使用#{...} 构造将任意 Ruby 表达式嵌入到模式中。
place = "東京都"
/#{place}/.match("Go to 東京都")
#=> #<MatchData "東京都">
字符类
character class 用方括号([、])分隔,并列出匹配中该点可能出现的字符。 /[ab]/ 表示 a 或 b ,而不是 /ab/ 表示 a 后跟 b 。
/W[aeiou]rd/.match("Word") #=> #<MatchData "Word">
在字符类中,连字符 (-) 是表示包含字符范围的元字符。 [abcd] 等同于 [a-d] 。一个范围后面可以跟另一个范围,因此 [abcdwxyz] 等价于 [a-dw-z] 。范围或单个字符出现在字符类中的顺序无关紧要。
/[0-9a-f]/.match('9f') #=> #<MatchData "9">
/[9f]/.match('9f') #=> #<MatchData "9">
如果字符类的第一个字符是插入符号 (^),则该类被反转:它匹配任何命名的字符 except。
/[^a-eg-z]/.match('f') #=> #<MatchData "f">
一个字符类可能包含另一个字符类。这本身没有用,因为 [a-z[0-9]] 说明了与 [a-z0-9] 相同的集合。但是,字符类也支持 && 运算符,该运算符对其参数执行集合交集。两者可以组合如下:
/[a-w&&[^c-g]z]/ # ([a-w] AND ([^c-g] OR z))
这相当于:
/[abh-w]/
以下元字符的行为也类似于字符类:
-
/./- 除换行符以外的任何字符。 -
/./m- 任何字符(m修饰符启用多行模式) -
/\w/- 一个单词字符([a-zA-Z0-9_]) -
/\W/- 非单词字符 ([^a-zA-Z0-9_])。如果使用带有/i修饰符的/\W/,请查看Bug #4044。 -
/\d/- 一个数字字符([0-9]) -
/\D/- 非数字字符([^0-9]) -
/\h/- 一个十六进制字符([0-9a-fA-F]) -
/\H/- 非十六进制字符 ([^0-9a-fA-F]) -
/\s/- 一个空格字符:/[ \t\r\n\f\v]/ -
/\S/- 非空白字符:/[^ \t\r\n\f\v]/ -
/\R/- A linebreak:\n,\v,\f,\r,\u0085(NEXT LINE),\u2028(LINE SEPARATOR),\u2029(段落分隔符) or\r\n.
POSIX bracket expressions 也类似于字符类。它们为上述内容提供了一种可移植的替代方案,另外还有一个好处是它们包含非 ASCII 字符。例如,/\d/ 仅匹配 ASCII 十进制数字 (0-9);而/[[:digit:]]/ 匹配Unicode Nd 类别中的任何字符。
-
/[[:alnum:]]/- 字母和数字字符 -
/[[:alpha:]]/- 字母字符 -
/[[:blank:]]/- 空格或制表符 -
/[[:cntrl:]]/- 控制字符 -
/[[:digit:]]/- 数字 -
/[[:graph:]]/- 非空白字符(不包括空格、控制字符和类似字符) -
/[[:lower:]]/- 小写字母字符 -
/[[:print:]]/- 类似于 [:graph:],但包含空格字符 -
/[[:punct:]]/- 标点符号 -
/[[:space:]]/- 空白字符([:blank:]、换行符、回车符等) -
/[[:upper:]]/- 大写字母 -
/[[:xdigit:]]/- 十六进制数字中允许的数字(即 0-9a-fA-F)
Ruby 还支持以下非 POSIX 字符类:
-
/[[:word:]]/- 以下 Unicode 通用类别之一中的字符Letter、Mark、Number、Connector_Punctuation -
/[[:ascii:]]/- ASCII 字符集中的一个字符# U+06F2 is "EXTENDED ARABIC-INDIC DIGIT TWO" /[[:digit:]]/.match("\u06F2") #=> #<MatchData "\u{06F2}"> /[[:upper:]][[:lower:]]/.match("Hello") #=> #<MatchData "He"> /[[:xdigit:]][[:xdigit:]]/.match("A6") #=> #<MatchData "A6">
重复
到目前为止说明的构造匹配单个字符。它们后面可以跟一个重复元字符来指定它们需要出现多少次。这样的元字符称为 quantifiers 。
-
*- 零次或多次 -
+- 一次或多次 -
?- 零次或一次(可选) -
{n}- 正好n次 -
{n,}-n或更多次 -
{,m}-m或更少次 -
{n,m}- 至少n和最多m次
至少一个大写字符 (‘H’),至少一个小写字符 (‘e’),两个 ‘l’ 字符,然后是一个 ‘o’:
"Hello".match(/[[:upper:]]+[[:lower:]]+l{2}o/) #=> #<MatchData "Hello">
贪心匹配
默认情况下,重复为greedy:匹配尽可能多的匹配项,同时仍允许整体匹配成功。相比之下,lazy 匹配使整体成功所需的匹配数量最少。大多数贪心的元字符可以通过使用 ? 来变得懒惰。对于{n} 模式,因为它指定要匹配的确切字符数而不是可变数量的字符,所以? 元字符反而使重复模式成为可选的。
下面的两种模式都匹配字符串。第一个使用贪心量词,所以‘.+’匹配‘<a><b>’;第二个使用惰性量词,所以'.+?'匹配'<a>':
/<.+>/.match("<a><b>") #=> #<MatchData "<a><b>">
/<.+?>/.match("<a><b>") #=> #<MatchData "<a>">
占有欲匹配
后跟 + 的量词匹配 possessively :一旦匹配,它就不会回溯。他们表现得像贪心的量词,但在匹配后他们拒绝 “give up” 他们的匹配,即使这会危及整体匹配。
/<.*><.+>/.match("<a><b>") #=> #<MatchData "<a><b>">
/<.*+><.+>/.match("<a><b>") #=> nil
/<.*><.++>/.match("<a><b>") #=> nil
捕获
括号可用于 capturing 。由第 n 组括号括起来的文本随后可以用 n 引用。在模式中使用 backreference \n(例如 \1 );在模式之外使用 MatchData[n] (例如 MatchData[1] )。
在此示例中,'at' 由第一组括号捕获,然后用 \1 引用:
/[csh](..) [csh]\1 in/.match("The cat sat in the hat")
#=> #<MatchData "cat sat in" 1:"at">
Regexp#match 返回一个 MatchData 对象,该对象通过其 [] 方法使捕获的文本可用:
/[csh](..) [csh]\1 in/.match("The cat sat in the hat")[1] #=> 'at'
虽然 Ruby 支持任意数量的编号捕获组,但使用 \n 反向引用语法仅支持组 1-9。
Ruby 还支持 \0 作为特殊的反向引用,它引用整个匹配的字符串。这也可以在 MatchData[0] 获得。注意\0反向引用不能在正则表达式内部使用,因为反向引用只能在捕获组结束后使用,而\0反向引用使用整个匹配的隐式捕获组。但是,您可以在进行替换时使用此反向引用:
"The cat sat in the hat".gsub(/[csh]at/, '\0s')
# => "The cats sats in the hats"
命名捕获
使用 (?< name >) 或 (?' name ') 构造定义时,可以按名称引用捕获组。
/\$(?<dollars>\d+)\.(?<cents>\d+)/.match("$3.67")
#=> #<MatchData "$3.67" dollars:"3" cents:"67">
/\$(?<dollars>\d+)\.(?<cents>\d+)/.match("$3.67")[:dollars] #=> "3"
命名组可以使用 \k< name > 进行反向引用,其中 name 是组名称。
/(?<vowel>[aeiou]).\k<vowel>.\k<vowel>/.match('ototomy')
#=> #<MatchData "ototo" vowel:"o">
注意:正则表达式不能同时使用命名反向引用和编号反向引用。此外,如果在正则表达式中使用命名捕获,则用于分组的括号会导致未命名捕获被视为非捕获。
/(\w)(\w)/.match("ab").captures # => ["a", "b"]
/(\w)(\w)/.match("ab").named_captures # => {}
/(?<c>\w)(\w)/.match("ab").captures # => ["a"]
/(?<c>\w)(\w)/.match("ab").named_captures # => {"c"=>"a"}
当命名捕获组与表达式左侧的文字正则表达式和 =~ 运算符一起使用时,捕获的文本也会分配给具有相应名称的局部变量。
/\$(?<dollars>\d+)\.(?<cents>\d+)/ =~ "$3.67" #=> 0
dollars #=> "3"
分组
括号还包括group 它们所包含的术语,允许将它们量化为一个整体atomic。
下面的模式匹配一个元音后跟 2 个单词字符:
/[aeiou]\w{2}/.match("Caenorhabditis elegans") #=> #<MatchData "aen">
而下面的模式匹配一个元音后跟一个单词字符,两次,即[aeiou]\w[aeiou]\w:‘enor’。
/([aeiou]\w){2}/.match("Caenorhabditis elegans")
#=> #<MatchData "enor" 1:"or">
(?: … ) 构造提供分组而不捕获。也就是说,它将它包含的术语组合成一个原子整体,而不创建反向引用。这以牺牲可读性为代价提高了性能。
第一组括号捕获‘n’ 和第二个‘ti’。第二组稍后使用反向引用 \2 引用:
/I(n)ves(ti)ga\2ons/.match("Investigations")
#=> #<MatchData "Investigations" 1:"n" 2:"ti">
第一组括号现在用'?:'设为非捕获,所以它仍然匹配‘n’,但不创建反向引用。因此,反向引用\1 现在引用‘ti’。
/I(?:n)ves(ti)ga\1ons/.match("Investigations")
#=> #<MatchData "Investigations" 1:"ti">
原子分组
可以使用 (?> pat ) 进行分组 atomic 。这会导致子表达式pat 独立于表达式的其余部分进行匹配,以便它匹配的内容对于匹配的其余部分变得固定,除非必须放弃整个子表达式并随后重新访问。这样,pat 被视为不可分割的整体。原子分组通常用于优化模式,以防止正则表达式引擎不必要地回溯。
下面模式中的 " 匹配字符串的第一个字符,然后 .* 匹配 Quote“ 。这会导致整体匹配失败,因此 .* 匹配的文本会回溯一个位置,从而使字符串的最后一个字符可用于匹配 "
/".*"/.match('"Quote"') #=> #<MatchData "\"Quote\"">
如果 .* 被原子分组,它拒绝回溯 Quote“ ,即使这意味着整体匹配失败
/"(?>.*)"/.match('"Quote"') #=> nil
子表达式调用
\g< name > 语法再次匹配名为 name 的前一个子表达式,它可以是组名或编号。这与反向引用的不同之处在于它重新执行组,而不是简单地尝试重新匹配相同的文本。
此模式匹配 ( 字符并将其分配给 paren 组,尝试再次调用 paren sub-expression 但失败,然后匹配文字 ) :
/\A(?<paren>\(\g<paren>*\))*\z/ =~ '()'
/\A(?<paren>\(\g<paren>*\))*\z/ =~ '(())' #=> 0
# ^1
# ^2
# ^3
# ^4
# ^5
# ^6
# ^7
# ^8
# ^9
# ^10
-
匹配字符串的开头,即第一个字符之前。
-
进入一个名为
paren的命名捕获组 -
匹配文字
(,即字符串中的第一个字符 -
再次调用
paren组,即递归回到第二步 -
重新进入
paren组 -
匹配文字
(,即字符串中的第二个字符 -
尝试第三次调用
paren,但失败,因为这样做会阻止整体成功匹配 -
匹配文字
),即字符串中的第三个字符。标记第二个递归调用的结束 -
匹配文字
),即字符串中的第四个字符 -
匹配字符串的结尾
交替
竖线元字符 (|) 将多个表达式组合成一个匹配任何表达式的表达式。每个表达式都是一个 alternative 。
/\w(and|or)\w/.match("Feliformia") #=> #<MatchData "form" 1:"or">
/\w(and|or)\w/.match("furandi") #=> #<MatchData "randi" 1:"and">
/\w(and|or)\w/.match("dissemblance") #=> nil
字符属性
\p{} 构造将字符与命名属性匹配,很像 POSIX 括号类。
-
/\p{Alnum}/- 字母和数字字符 -
/\p{Alpha}/- 字母字符 -
/\p{Blank}/- 空格或制表符 -
/\p{Cntrl}/- 控制字符 -
/\p{Digit}/- 数字 -
/\p{Graph}/- 非空白字符(不包括空格、控制字符和类似字符) -
/\p{Lower}/- 小写字母字符 -
/\p{Print}/- 与\p{Graph}类似,但包含空格字符 -
/\p{Punct}/- 标点符号 -
/\p{Space}/- 空白字符([:blank:]、换行符、回车符等) -
/\p{Upper}/- 大写字母 -
/\p{XDigit}/- 十六进制数字中允许的数字(即 0-9a-fA-F) -
/\p{Word}/- 以下 Unicode 通用类别之一的成员Letter、Mark、Number、Connector_Punctuation -
/\p{ASCII}/- ASCII 字符集中的一个字符 -
/\p{Any}/- 任何 Unicode 字符(包括未分配的字符) -
/\p{Assigned}/- 指定的字符
Unicode 字符的 General Category 值也可以与 \p{ Ab } 匹配,其中 Ab 是类别的缩写,如下所述:
-
/\p{L}/- “信” -
/\p{Ll}/- “字母:小写” -
/\p{Lm}/- “字母:马克” -
/\p{Lo}/- “信:其他” -
/\p{Lt}/- “字母:标题” -
/\p{Lu}/- '字母:大写 -
/\p{Lo}/- “信:其他” -
/\p{M}/- “标记” -
/\p{Mn}/- “标记:无间距” -
/\p{Mc}/- “标记:间距组合” -
/\p{Me}/- “标记:封闭” -
/\p{N}/- “数字” -
/\p{Nd}/- “数字:十进制数字” -
/\p{Nl}/- “数字:字母” -
/\p{No}/- “编号:其他” -
/\p{P}/- “标点符号” -
/\p{Pc}/- “标点符号:连接符” -
/\p{Pd}/- “标点符号:破折号” -
/\p{Ps}/- “标点符号:打开” -
/\p{Pe}/- “标点符号:关闭” -
/\p{Pi}/- “标点符号:初始引用” -
/\p{Pf}/- “标点符号:最后引述” -
/\p{Po}/- “标点符号:其他” -
/\p{S}/- “符号” -
/\p{Sm}/- “符号:数学” -
/\p{Sc}/- “符号:货币” -
/\p{Sc}/- “符号:货币” -
/\p{Sk}/- “符号:修饰符” -
/\p{So}/- “符号:其他” -
/\p{Z}/- “分隔符” -
/\p{Zs}/- “分隔符:空格” -
/\p{Zl}/- “分隔符:线” -
/\p{Zp}/- “分隔符:段落” -
/\p{C}/- “其他” -
/\p{Cc}/- “其他:控制” -
/\p{Cf}/- “其他:格式” -
/\p{Cn}/- “其他:未分配” -
/\p{Co}/- “其他:私人使用” -
/\p{Cs}/- “其他:代理”
最后,\p{}匹配字符的 Unicodescript.支持以下脚本:Arabic,Armenian,Balinese,Bengali,Bopomofo,Braille,Buginese,Buhid,Canadian_Aboriginal,Carian,Cham,Cherokee,Common,Coptic,Cuneiform,Cypriot,Cyrillic,Deseret,Devanagari,Ethiopic,Georgian,Glagolitic,Gothic,Greek,Gujarati,Gurmukhi,Han,Hangul,Hanunoo,Hebrew,Hiragana,Inherited,Kannada,Katakana,Kayah_Li,Kharoshthi,Khmer,Lao,Latin,Lepcha,Limbu,Linear_B,Lycian,Lydian,Malayalam,Mongolian,Myanmar,New_Tai_Lue,Nko,Ogham,Ol_Chiki,Old_Italic,Old_Persian,Oriya,Osmanya,Phags_Pa,Phoenician,Rejang,Runic,Saurashtra,Shavian,Sinhala,Sundanese,Syloti_Nagri,Syriac,Tagalog,Tagbanwa,Tai_Le,Tamil,Telugu,Thaana,Thai,Tibetan,Tifinagh,Ugaritic,Vai, 和Yi.
Unicode 代码点 U+06E9 被命名为“ARABIC PLACE OF SAJDAH”,属于阿拉伯文字:
/\p{Arabic}/.match("\u06E9") #=> #<MatchData "\u06E9">
所有字符属性都可以通过在其名称前加上插入符号 (^) 来反转。
字母“A”不在 Unicode Ll(字母;小写)类别中,因此该匹配成功:
/\p{^Ll}/.match("A") #=> #<MatchData "A">
锚点
锚点是匹配字符之间的zero-width 位置的元字符,anchoring 匹配特定位置的元字符。
-
^- 匹配行首 -
$- 匹配行尾 -
\A- 匹配字符串的开头。 -
\Z- 匹配字符串结尾。如果字符串以换行符结尾,则在换行符之前匹配 -
\z- 匹配字符串结尾 -
\G- 匹配第一个匹配位置:在
String#gsub和String#scan等方法中,它会在每次迭代时发生变化。它最初匹配主题的开头,并且在随后的每次迭代中,它匹配最后一次匹配完成的位置。" a b c".gsub(/ /, '_') #=> "____a_b_c" " a b c".gsub(/\G /, '_') #=> "____a b c"在像
Regexp#match和String#match这样采用(可选)偏移量的方法中,它匹配搜索开始的位置。"hello, world".match(/,/, 3) #=> #<MatchData ","> "hello, world".match(/\G,/, 3) #=> nil -
\b- 在括号外匹配单词边界;括号内的退格 (0x08) -
\B- 匹配非单词边界 -
(?=pat)-Positive lookahead断言:确保以下字符匹配pat,但不包括匹配文本中的这些字符 -
(?!pat)-Negative lookahead断言:确保以下字符不匹配pat,但不包括匹配文本中的这些字符 -
(?<=pat)-Positive lookbehind断言:确保前面的字符匹配pat,但不包括匹配文本中的这些字符 -
(?<!pat)-Negative lookbehind断言:确保前面的字符不匹配pat,但在匹配的文本中不包含这些字符 -
\K- 在正则表达式中使用\K之前的内容的正向后视。例如,以下两个正则表达式几乎是等价的:/ab\Kc/ /(?<=ab)c/以下两个正则表达式也是如此:
/(a)\K(b)\Kc/ /(?<=(?<=(a))(b))c/
如果模式没有锚定,它可以从字符串中的任何点开始:
/real/.match("surrealist") #=> #<MatchData "real">
将模式锚定到字符串的开头会强制匹配从那里开始。 ‘real’ 没有出现在字符串的开头,所以现在匹配失败:
/\Areal/.match("surrealist") #=> nil
下面的匹配失败,因为尽管“Demand”包含‘and’,但该模式不会出现在单词边界处。
/\band/.match("Demand")
而在以下示例中,‘and’ 已锚定到非单词边界,因此它不是匹配第一个 ‘and’,而是匹配 ‘demand’ 的第四个字母:
/\Band.+/.match("Supply and demand curve") #=> #<MatchData "and curve">
下面的模式使用正向前瞻和正向后视来匹配出现在标签中的文本,而不包括匹配中的标签:
/(?<=<b>)\w+(?=<\/b>)/.match("Fortune favours the <b>bold</b>")
#=> #<MatchData "bold">
选项
正则表达式的结束分隔符后面可以跟一个或多个 single-letter 选项,这些选项控制模式如何匹配。
-
/pat/i- 忽略大小写 -
/pat/m- 将换行符视为与.匹配的字符 -
/pat/x- 忽略模式中的空格和注释 -
/pat/o- 仅执行一次#{}插值
i 、 m 和 x 也可以通过 (? on - off ) 构造应用于子表达式级别,该构造启用选项 on 并禁用选项 off 用于括号括起来的表达式:
/a(?i:b)c/.match('aBc') #=> #<MatchData "aBc">
/a(?-i:b)c/i.match('ABC') #=> nil
此外,还可以为模式的其余部分切换这些选项:
/a(?i)bc/.match('abC') #=> #<MatchData "abC">
选项也可以与 Regexp.new 一起使用:
Regexp.new("abc", Regexp::IGNORECASE) #=> /abc/i
Regexp.new("abc", Regexp::MULTILINE) #=> /abc/m
Regexp.new("abc # Comment", Regexp::EXTENDED) #=> /abc # Comment/x
Regexp.new("abc", Regexp::IGNORECASE | Regexp::MULTILINE) #=> /abc/mi
Free-Spacing 模式和评论
如上所述,x 选项启用free-spacing 模式。模式内的文字空白将被忽略,并且 octothorpe (#) 字符引入了注释,直到行尾。这允许以可能更易读的方式组织模式的组件。
一个人为的模式来匹配带有可选小数位的数字:
float_pat = /\A
[[:digit:]]+ # 1 or more digits before the decimal point
(\. # Decimal point
[[:digit:]]+ # 1 or more digits after the decimal point
)? # The decimal point and following digits are optional
\Z/x
float_pat.match('3.14') #=> #<MatchData "3.14" 1:".14">
有许多匹配空格的策略:
-
使用
\s或\p{Space}等模式。 -
使用转义的空格,例如
\,即前面有反斜杠的空格。 -
使用字符类,例如
[ ]。
注释可以包含在具有 (?# comment ) 构造的非 x 模式中,其中 comment 是正则表达式引擎忽略的任意文本。
正则表达式文字中的注释不能包含未转义的终止符。
Encoding
假设正则表达式使用源编码。这可以用以下修饰符之一覆盖。
-
/pat/u- UTF-8 -
/pat/e- EUC-JP -
/pat/s- Windows-31J -
/pat/n- ASCII-8BIT
当一个正则表达式共享一个编码,或者正则表达式的编码是US-ASCII并且字符串的编码是ASCII-compatible时,一个正则表达式可以与一个字符串匹配。
如果尝试匹配不兼容的编码,则会引发 Encoding::CompatibilityError 异常。
Regexp#fixed_encoding? 谓词指示正则表达式是否具有 fixed 编码,即与 ASCII 不兼容的编码。正则表达式的编码可以通过提供 Regexp::FIXEDENCODING 作为 Regexp.new 的第二个参数来显式修复:
r = Regexp.new("a".force_encoding("iso-8859-1"),Regexp::FIXEDENCODING)
r =~ "a\u3042"
# raises Encoding::CompatibilityError: incompatible encoding regexp match
# (ISO-8859-1 regexp with UTF-8 string)
特殊的全局变量
模式匹配设置一些全局变量:
-
$~等价于Regexp.last_match; -
$&包含完整匹配的文本; -
$`包含匹配前的字符串; -
$'包含匹配后的字符串; -
$1、$2等包含文本匹配第一、第二等捕获组; -
$+包含最后一个捕获组。
例子:
m = /s(\w{2}).*(c)/.match('haystack') #=> #<MatchData "stac" 1:"ta" 2:"c">
$~ #=> #<MatchData "stac" 1:"ta" 2:"c">
Regexp.last_match #=> #<MatchData "stac" 1:"ta" 2:"c">
$& #=> "stac"
# same as m[0]
$` #=> "hay"
# same as m.pre_match
$' #=> "k"
# same as m.post_match
$1 #=> "ta"
# same as m[1]
$2 #=> "c"
# same as m[2]
$3 #=> nil
# no third group in pattern
$+ #=> "c"
# same as m[-1]
这些全局变量是线程局部变量和method-local 变量。
性能
某些结构的病态组合会导致性能极差。
考虑一个由 25 个 a 、一个 d 、4 个 a 和一个 c 组成的字符串。
s = 'a' * 25 + 'd' + 'a' * 4 + 'c'
#=> "aaaaaaaaaaaaaaaaaaaaaaaaadaaaac"
如您所料,以下模式会立即匹配:
/(b|a)/ =~ s #=> 0
/(b|a+)/ =~ s #=> 0
/(b|a+)*/ =~ s #=> 0
但是,以下模式需要更长的时间:
/(b|a+)*c/ =~ s #=> 26
发生这种情况是因为正则表达式中的原子被立即数 + 和封闭的 * 量化,没有什么可以区分哪个控制任何特定字符。产生的不确定性产生super-linear 性能。 (请参阅 Mastering Regular Expressions(第 3 版),第 222 页,Jeffery Friedl 进行 in-depth 分析)。这种特殊情况可以通过使用原子分组来修复,从而防止不必要的回溯:
(start = Time.now) && /(b|a+)*c/ =~ s && (Time.now - start)
#=> 24.702736882
(start = Time.now) && /(?>b|a+)*c/ =~ s && (Time.now - start)
#=> 0.000166571
以下示例代表了类似的情况,对我来说执行大约需要 60 秒:
将 29 个 a 的字符串与 29 个可选的 a 后跟 29 个强制的 a 的模式匹配:
Regexp.new('a?' * 29 + 'a' * 29) =~ 'a' * 29
29 个可选的 a 匹配字符串,但这会阻止后面的 29 个强制 a 匹配。然后 Ruby 必须反复回溯,以便在仍然匹配强制 29 的同时尽可能多地满足可选匹配。很明显,没有一个可选匹配可以成功,但不幸的是,Ruby 忽略了这一事实。
提高性能的最佳方法是显著减少所需的回溯量。对于这种情况,不是单独匹配 29 个可选的 a s,而是可以使用 a{0,29} 一次匹配一系列可选的 a s:
Regexp.new('a{0,29}' + 'a' * 29) =~ 'a' * 29
相关用法
- Ruby Regexp named_captures()用法及代码示例
- Ruby Regexp to_s()用法及代码示例
- Ruby Regexp.eql?用法及代码示例
- Ruby Regexp hash()用法及代码示例
- Ruby Regexp.fixed_encoding?用法及代码示例
- Ruby Regexp.options用法及代码示例
- Ruby Regexp inspect()用法及代码示例
- Ruby Regexp.inspect用法及代码示例
- Ruby Regexp.names用法及代码示例
- Ruby Regexp source()用法及代码示例
- Ruby Regexp match()用法及代码示例
- Ruby Regexp.rxp =~ str用法及代码示例
- Ruby Regexp.escape用法及代码示例
- Ruby Regexp.casefold?用法及代码示例
- Ruby Regexp.match用法及代码示例
- Ruby Regexp.try_convert用法及代码示例
- Ruby Regexp encoding()用法及代码示例
- Ruby Regexp fixed_encoding?()用法及代码示例
- Ruby Regexp escape()用法及代码示例
- Ruby Regexp names()用法及代码示例
- Ruby Regexp new()用法及代码示例
- Ruby Regexp.rxp ===用法及代码示例
- Ruby Regexp.match?用法及代码示例
- Ruby Regexp quote()用法及代码示例
- Ruby Regexp.union用法及代码示例
注:本文由纯净天空筛选整理自ruby-lang.org大神的英文原创作品 Regexp类。非经特殊声明,原始代码版权归原作者所有,本译文未经允许或授权,请勿转载或复制。
