Why does capturing named groups in Ruby result in

2020-07-17 08:17发布

问题:

I am having trouble with named captures in regular expressions in Ruby 2.0. I have a string variable and an interpolated regular expression:

str = "hello world"
re = /\w+/
/(?<greeting>#{re})/ =~ str
greeting

It raises the following exception:

prova.rb:4:in <main>': undefined local variable or methodgreeting' for main:Object (NameError)
shell returned 1

However, the interpolated expression works without named captures. For example:

/(#{re})/ =~ str
$1
# => "hello"

回答1:

Named Captures Must Use Literals

You are encountering some limitations of Ruby's regular expression library. The Regexp#=~ method limits named captures as follows:

  • The assignment does not occur if the regexp is not a literal.
  • A regexp interpolation, #{}, also disables the assignment.
  • The assignment does not occur if the regexp is placed on the right hand side.

You'll need to decide whether you want named captures or interpolation in your regular expressions. You currently cannot have both.



回答2:

Assign the result of #match; this will be accessible as a hash that allows you to look up your named capture groups:

> matches = "hello world".match(/(?<greeting>\w+)/)
=> #<MatchData "hello" greeting:"hello">
> matches[:greeting]
=> "hello"

Alternately, give #match a block, which will receive the match results:

> "hello world".match(/(?<greeting>\w+)/) {|matches| matches[:greeting] }
=> "hello"


回答3:

As an addendum to both answers in order to make it crystal clear:

str = "hello world"
# => "hello world"
re = /\w+/
# => /\w+/
re2 = /(?<greeting>#{re})/
# => /(?<greeting>(?-mix:\w+))/
md = re2.match str
# => #<MatchData "hello" greeting:"hello">
md[:greeting]
# => "hello"

Interpolation is fine with named captures, just use the MatchData object, most easily returned via match.