Ruby正则表达式分组匹配,一行内赋值变量

140

我目前正在尝试将一个字符串拆分成多个变量。 例如字符串:

ryan_string = "RyanOnRails: This is a test"

我已经使用这个正则表达式进行匹配,其中包含3个组:

ryan_group = ryan_string.scan(/(^.*)(:)(.*)/i)

现在要访问每个组,我必须像这样做:

ryan_group[0][0] (first group) RyanOnRails
ryan_group[0][1] (second group) :
ryan_group[0][2] (third group) This is a test

这似乎相当荒谬,感觉我做错了什么。我期望能够像这样做些事情:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)

这是否可能?还有比我当前的方法更好的方式吗?

5个回答

231

对于这个问题,你不需要使用scan方法,因为这样做没有多大意义。你可以使用String#match方法,它将返回一个MatchData对象,然后你可以调用#captures方法来返回一个捕获数组。像这样:

#!/usr/bin/env ruby

string = "RyanOnRails: This is a test"
one, two, three = string.match(/(^.*)(:)(.*)/i).captures

p one   #=> "RyanOnRails"
p two   #=> ":"
p three #=> " This is a test"

需要注意的是,如果未找到匹配项,String#match将返回nil,因此以下代码可能更加可靠:

if match = string.match(/(^.*)(:)(.*)/i)
  one, two, three = match.captures
end

虽然对于这种情况来说使用 scan 不太合理。但它仍然可以完成任务,你只需要先将返回的数组展开即可。one, two, three = string.scan(/(^.*)(:)(.*)/i).flatten


7
注意,如果没有匹配项,match 将返回 nil,此时您将获得一个 NilError。如果您使用的是 Rails,请建议您将以下语句更改为:`one, two, three = string.match(/(^.*)(:)(.*)/i).try(:captures)`这样做可以避免出现错误。 - Andrea Salicetti
5
@AndreaSalicetti,我已经修改了我的帖子,没有添加特定于Rails的代码,因此我使用了一个处理返回的nil对象的版本进行了修改。 - Lee Jarvis
7
你可以使用新的&.运算符将其放回一行,甚至在只有一个捕获组时使用两次。例如,string.match(regex)&.captures&.first - Gerry Shaw

49
您可以使用Match或=~代替,这将为您提供单个匹配项,您可以以相同的方式访问匹配数据,或者只使用特殊的匹配变量$1、$2、$3。
类似于:
if ryan_string =~ /(^.*)(:)(.*)/i
   first = $1
   third = $3
end

5
@Gaston,那实际上是源自Perl的原始正则表达式语法 :) - ohaleck

35
你可以给捕获的匹配命名。
string = "RyanOnRails: This is a test"
/(?<one>^.*)(?<two>:)(?<three>.*)/i =~ string
puts one, two, three

如果您颠倒字符串和正则表达式的顺序,它将不起作用。


7

你需要决定这是否是一个好主意,但Ruby的正则表达式可以(自动)为你定义本地变量

我还不确定这个功能是很棒还是完全疯狂,但是你的正则表达式可以定义本地变量。

ryan_string = "RyanOnRails: This is a test"
/^(?<webframework>.*)(?<colon>:)(?<rest>)/ =~ ryan_string
# This defined three variables for you. Crazy, but true.
webframework # => "RyanOnRails"
puts "W: #{webframework} , C: #{colon}, R: #{rest}"

请查看http://ruby-doc.org/core-2.1.1/Regexp.html,搜索"local variable"。
注意: 正如评论中所指出的那样,我发现有一个相似且早期的问题答案由@toonsend (https://dev59.com/R2ox5IYBdhLWcg3wSCfk#21412455)提供。我不认为我在“窃取”,但如果你想公平地赞扬并尊重第一个答案,随意 :) 我希望没有动物受到伤害。

这个答案看起来非常类似于https://dev59.com/R2ox5IYBdhLWcg3wSCfk#21412455,而且比那个回答早了一年... - Brad Werth
@BradWerth 我想我刚没看到那个。但是我更新了我的答案,包括你的顾虑。 - Felix

6

scan()会在你的字符串中找到正则表达式的所有不重叠匹配项,因此它返回的是一个数组的数组,而不是你期望的组数组。

你最好使用match(),然后使用MatchData#captures获取捕获数组:

g1, g2, g3 = ryan_string.match(/(^.*)(:)(.*)/i).captures

如果您想的话,也可以使用scan()来完成此操作:

g1, g2, g3 = ryan_string.scan(/(^.*)(:)(.*)/i)[0]

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