Ruby一行代码捕获正则表达式匹配

7
在Perl中,我使用以下的一行语句通过正则表达式从字符串中提取匹配项并将其赋值。这个语句查找单个匹配项并将其赋值给一个字符串:
my $string = "the quick brown fox jumps over the lazy dog.";

my $extractString = ($string =~ m{fox (.*?) dog})[0];

结果:$extractString == '跳过那只懒狗'

以下代码可以从多个匹配项中创建一个数组:

my $string = "the quick brown fox jumps over the lazy dog.";

my @extractArray = $string =~ m{the (.*?) fox .*?the (.*?) dog};

结果: @extractArray == ['quick brown', 'lazy']

在Ruby中是否有相应的方法可以创建这些单行代码?

3个回答

11
string = "the quick brown fox jumps over the lazy dog."

extract_string = string[/fox (.*?) dog/, 1]
# => "jumps over the lazy"

extract_array = string.scan(/the (.*?) fox .*?the (.*?) dog/).first
# => ["quick brown", "lazy"]

如果没有找到匹配项,这种方法也会返回nil(而不是抛出错误)。

extract_string = string[/MISSING_CAT (.*?) dog/, 1]
# => nil

extract_array = string.scan(/the (.*?) MISSING_CAT .*?the (.*?) dog/).first
# => nil

8
使用 String#matchMatchData#[]MatchData#captures 来获取匹配的反向引用。
s = "the quick brown fox jumps over the lazy dog."

s.match(/fox (.*?) dog/)[1]
# => "jumps over the lazy"
s.match(/fox (.*?) dog/).captures
# => ["jumps over the lazy"]

s.match(/the (.*?) fox .*?the (.*?) dog/)[1..2]
# => ["quick brown", "lazy"]
s.match(/the (.*?) fox .*?the (.*?) dog/).captures
# => ["quick brown", "lazy"]

更新

为了避免undefined method []错误:

(s.match(/fox (.*?) cat/) || [])[1]
# => nil
(s.match(/the (.*?) fox .*?the (.*?) cat/) || [])[1..2]
# => nil
(s.match(/the (.*?) fox .*?the (.*?) cat/) || [])[1..-1] # instead of .captures
# => nil

发现了使用s.match(/fox (.*?) dog/)[1]方法的问题。如果没有找到匹配项,Ruby 2.1会抛出“undefined method '[]'”错误。例如,将其更改为s.match(/fox (.*?) cat/)[1]就会出错。 - Alan W. Smith
@AlanW.Smith,我更新了答案。(s.match(/fox (.*?) cat/) || [])[1] - falsetru

3
首先,在编写Ruby代码时,不要过于依赖Perl思维方式。我们会更加详细地表达来使代码更易读。
我会将my @extractArray = $string =~ m{the (.*?) fox .*?the (.*?) dog};写成如下形式:
string = "the quick brown fox jumps over the lazy dog."

string[/the (.*?) fox .*?the (.*?) dog/]
extract_array = $1, $2
# => ["quick brown", "lazy"]

Ruby和Perl一样,可以识别捕获组并将其分配给值$1$2等。这使得在获取值并稍后分配它们时非常清晰明了。正则表达式引擎还允许您创建和分配命名捕获,但它们往往会模糊视听,所以为了清晰起见,我倾向于采用这种方式。
我们也可以使用match来实现:
/the (.*?) fox .*?the (.*?) dog/.match(string) # => #<MatchData "the quick brown fox jumps over the lazy dog" 1:"quick brown" 2:"lazy">

但最终结果更易读吗?
extract_array = /the (.*?) fox .*?the (.*?) dog/.match(string)[1..-1] 
# => ["quick brown", "lazy"]

命名捕获也很有趣:

/the (?<quick_brown>.*?) fox .*?the (?<lazy>.*?) dog/ =~ string
quick_brown # => "quick brown"
lazy # => "lazy"

但它们导致我们不知道这些变量是在哪里初始化和赋值的;肯定不会在正则表达式中寻找它们的出现,因此对他人来说可能会产生混淆,并再次成为一个维护问题。


Cary说:

稍微解释一下命名捕获,如果match_data = string.match /the (?.?) fox .?the (?.*?) dog/,那么match_data [: quick_brown] # =>“quick brown”,match_data [: lazy] # =>“lazy”(以及quick_brown # =>“quick brown”和lazy # =>“lazy”)。有了命名捕获,我认为没有必要使用全局变量或Regexp.last_match等。

是的,但也有一些味道。

我们可以使用values_atmatch的MatchData结果来检索捕获的值,但是类中存在一些令人困惑的行为,这让我不感兴趣:

/the (?<quick_brown>.*?) fox .*?the (?<lazy>.*?) dog/.match(string)['lazy']

可以正常工作,这意味着MatchData知道如何像Hash一样运作:

{'lazy' => 'dog'}['lazy'] # => "dog"

它有一个values_at方法,类似于Hash,但它的工作方式不直观:

/the (?<quick_brown>.*?) fox .*?the (?<lazy>.*?) dog/.match(string).values_at('lazy') # => 
# ~> -:6:in `values_at': no implicit conversion of String into Integer (TypeError)

鉴于:

/the (?<quick_brown>.*?) fox .*?the (?<lazy>.*?) dog/.match(string).values_at(2) # => ["lazy"]

现在它的行为类似于一个数组:

['all captures', 'quick brown', 'lazy'].values_at(2) # => ["lazy"]

我想要一致性,这让我感到头疼。


1
我认为将匹配结果保存在全局变量中并不是一种良好的编程习惯,这只是从Perl继承下来的坏习惯。如果可以避免使用全局变量,那就更好了。我个人有过这样的经历,当我在嵌套代码中进行正则表达式匹配时,尝试访问全局匹配变量会导致混乱。 - sawa
1
那些“全局变量”无论如何都在那里,它们并不是因为被用于赋值而出现的。是的,它们是 Perl 的特色之一,但也是其中更有用的一个。它们会被任何后续使用正则表达式的操作覆盖,包括在调用的子程序中,所以记住它们是易变的很重要;立即获取它们,不要指望它们在其他地方可用。 - the Tin Man
我知道它们存在。但你可以忽略它们。你无法避免它们的存在,但你可以忘记它们,这会使代码更清晰。 - sawa
稍微详细解释一下命名捕获,如果 match_data = string.match /the (?<quick_brown>.*?) fox .*?the (?<lazy>.*?) dog/,那么 match_data[:quick_brown] # => "quick brown"match_data[:lazy] # => "lazy"(以及 quick_brown # => "quick brown"lazy # => "lazy")。有了命名捕获,我认为没有必要使用全局变量或 Regexp.last_match 等。 - Cary Swoveland
1
除了现在有很多1.8.7的安装程序在使用,特别是在共享主机上,无法使用它们。 1.8.7不受Ruby开发人员支持,但仍然安装在我工作的许多主机上,并且这些主机被更多的用户使用。 这是互联网世界的生活。此外,尽管使用$1似乎过时或危险,但它还要继续一段时间。有很多Ruby代码在使用它。那个弃用过程将是痛苦的。 - the Tin Man

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