Ruby正则表达式中的非捕获组

12

我正在尝试从字符串中获取ID号码,比如说

id/number/2000GXZ2/ref=sr

使用

(?:id\/number\/)([a-zA-Z0-9]{8})

由于某种原因,非捕获组没有起作用,给出了以下结果:

id/number/2000GXZ2

2
你是如何得到结果的? 你需要获取第一个捕获组。 - Tushar
4
非捕获组的作用是将内容进行分组,但不会生成捕获结果,同时不会从全局匹配中删除已匹配的子字符串。 - Casimir et Hippolyte
1
如果您正确获取了http://rubular.com/r/6NcnBGTEwy,则它可以正常工作。 - Nermin
1
一个正则表达式本身并不会做任何事情。你是如何使用这个正则表达式的?用它来分割字符串吗?还是用它替换字符串的某些部分? - sawa
你需要从这个中获取什么信息:id/number/2000GXZ2/ref=sr?(哪一部分) - Shafizadeh
3个回答

16

正如其他人所提到的,非捕获组仍然计入总匹配次数。如果您不想在匹配中包含该部分,请使用后顾(lookbehind)。

Rubular 示例
(?<=id\/number\/)([a-zA-Z0-9]{8})
(?<=pat) - 正向零宽断言:确保前面的字符与 pat 匹配,但不将这些字符包含在匹配文本中。

Ruby Doc Regexp

此外,在这种情况下,id号码周围的捕获组是不必要的。


5

您拥有:

str = "id/number/2000GXZ2/ref=sr"

r = /
    (?:id\/number\/) # match string in a non-capture group
    ([a-zA-Z0-9]{8}) # match character in character class 8 times, in capture group 1
    /x               # extended/free-spacing regex definition mode

然后(使用 String#[]):

str[r]
  #=> "id/number/2000GXZ2"

返回整个匹配,而不仅仅是第一个捕获组的内容。有几种方法可以解决这个问题。首先考虑不使用捕获组的方法。

@jacob.m 建议使用正向后顾(稍微修改了他的代码)来处理第一个部分:

r = /
    (?<=id\/number\/) # match string in positive lookbehind
    [[:alnum:]]{8}    # match >= 1 alphameric characters
    /x

str[r]
  #=> "2000GXZ2"

一种替代方案是:
r = /
    id\/number\/   # match string
    \K             # forget everything matched so far
    [[:alnum:]]{8} # match 8 alphanumeric characters
    /x

str[r]
  #=> "2000GXZ2"
\K在匹配长度可变的情况下特别有用,因为(在Ruby中)正向后瞻不适用于可变长度的匹配。
对于这两种方法,如果要匹配的部分仅包含数字和大写字母,则可以使用[A-Z0-9]+而非[[:alnum:]](尽管后者包括Unicode字母,而不仅仅是英文字母)。实际上,如果所有条目都具有您的示例形式,则可能能够使用:
r = /
    \d          # match a digit
    [A-Z0-9]{7} # match >= 0 capital letters or digits
    /x

str[r]
  #=> "2000GXZ2"

另一种方法是保留你的捕获组。一种简单的方法是:
r = /
    id\/number\/     # match string
    ([[:alnum:]]{8}) # match >= 1 alphameric characters in capture group 1
    /x

str =~ r
str[r, 1] #=> "2000GXZ2"

另外,您可以使用String#sub方法将整个字符串替换为捕获组的内容:

r = /
    id\/number\/     # match string
    ([[:alnum:]]{8}) # match >= 1 alphameric characters in capture group 1
    .*               # match the remainder of the string
    /x

str.sub(r, '\1')  #=> "2000GXZ2"
str.sub(r, "\\1") #=> "2000GXZ2" 
str.sub(r) { $1 } #=> "2000GXZ2"

哇,谢谢你如此详尽的回答。非常感激! - AKarpun

0

这是关于 Ruby Regexp 的匹配一致性问题。有些 Regexp 风格的方法会返回全局匹配,而其他方法则会返回指定的匹配。

在这种情况下,我们可以使用一个方法来获得您想要的行为,那就是 scan

我认为没有人在这里实际上提到如何让您最初打算的 Regexp 正常工作,即获取仅捕获匹配项。要做到这一点,您可以使用以下方式使用原始模式的 scan 方法:

test_me.rb

test_string="id/number/2000GXZ2/ref=sr"
result = test_string.scan(/(?:id\/number\/)([a-zA-Z0-9]{8})/)
puts result
2000GXZ2

话虽如此,但是将非捕获组的 (?:) 替换为正向零宽断言的 (?<=),无论是在使用 scan 还是其他使用 Regexp 的 Ruby 部分,都会对您有所裨益。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接