如何将 Regexp.last_match 传递给 Ruby 中的块

How to pass Regexp.last_match to a block in Ruby

有没有办法将最后一场比赛(实际上Regexp.last_match)传递给Ruby中的块(迭代器)?

这里有一个示例方法作为 Srring#sub 的一种包装器来演示问题。它接受标准参数和块:

def newsub(str, *rest, &bloc)
  str.sub(*rest, &bloc)
end

它适用于标准的仅参数情况,并且可以占用一个块;然而,像 $1、$2 等位置特殊变量在块内是不可用的。以下是一些示例:

newsub("abcd", /ab(c)/, '')        # => "cd"
newsub("abcd", /ab(c)/){|m| }      # => "d"  ( == nil)
newsub("abcd", /ab(c)/){.upcase}   # => NoMethodError

该块无法以与 String#sub(/..(.)/){} 相同的方式工作的原因是我认为与范围有关;特殊变量 $1、$2 等是局部变量(Regexp.last_match)。

有什么办法可以解决吗?我想让方法 newsubString#sub 一样工作,从某种意义上说,$1、$2 等在提供的块中可用。

编辑:根据 some past answers,可能没有办法实现这个……

根据问题 (Ruby 2),这是一种方法。它不漂亮,在各个方面都不是 100% 完美,但可以做到。

def newsub(str, *rest, &bloc)
  str =~ rest[0]  # => ArgumentError if rest[0].nil?
  bloc.binding.tap do |b|
    b.local_variable_set(:_, $~)
    b.eval("$~=_")
  end if bloc
  str.sub(*rest, &bloc)
end

这样,结果如下:

_ = (/(xyz)/ =~ 'xyz')
p   # => "xyz"
p _   # => 0

p newsub("abcd", /ab(c)/, '')        # => "cd"
p   # => "xyz"
p _   # => 0

p newsub("abcd", /ab(c)/){|m| }      # => "cd"
p   # => "c"
p _                 # => #<MatchData "abc" 1:"c">

v, _ = , newsub("efg", /ef(g)/){.upcase}
p [v, _]  # => ["c", "G"]
p   # => "g"
p Regexp.last_match # => #<MatchData "efg" 1:"g">

深入分析

在上面定义的方法newsub中,当给定一个block时,调用者线程中的局部变量$1等在block执行后被(重新)设置,这与String#sub。但是,当没有给出块时,局部变量 $1 etc not 重置,而在 String#sub 中,无论是否给出块,$1 etc 总是被重置.

此外,调用者的局部变量_在此算法中被重置。在 Ruby 的约定中,局部变量 _ 用作虚拟变量,不应读取或引用其值。因此,这不会造成任何实际问题。如果语句 local_variable_set(:$~, $~) 有效,则不需要临时局部变量。然而,它不是,在 Ruby 中(至少从版本 2.5.1 开始)。请参阅 Kazuhiro NISHIYAMA 在 [ruby-list:50708].

中的评论(日语)

一般背景(Ruby 的规范)解释

这里有一个简单的例子来强调 Ruby 与这个问题相关的规范:

s = "abcd"
/b(c)/ =~ s
p      # => "c"
1.times do |i|
  p s    # => "abcd"
  p    # => "c"
end

$&</code>、<code>等特殊变量(相关,$~Regexp.last_match),$'等) 在本地范围内工作。在Ruby中,局部作用域继承父作用域中的同名变量。 在上面的例子中,变量s继承</code>也是如此。 <code>do 块由 1.times yield 编辑,方法 1.times 无法控制块内的变量,除了块参数(上例中的in.b.,虽然Integer#times没有提供任何块参数,但尝试接收一个块将被默默地忽略)。

这意味着yield-s一个block无法控制block中的</code>,<code>等的方法,这些都是局部变量(尽管它们可能看起来像全局变量)。

字符串大小写#sub

现在,让我们分析一下 String#sub 块的工作原理:

'abc'.sub(/.(.)./){ |m|  }

这里,方法sub首先进行正则表达式匹配,因此像</code>这样的局部变量被自动设置。然后,它们(像<code>这样的变量)在块中被继承,因为这个块与方法"sub"在相同的范围内。它们未从sub传递到块,不同于块参数m(这是一个匹配的字符串,或等同于$& ).

因此,如果方法 sub 定义在与块 不同的作用域 中,则 sub 方法无法控制局部变量块内,包括 </code>。 <em>不同的作用域</em> 表示 <code>sub 方法是用 Ruby 代码编写和定义的情况,或者实际上,所有 Ruby 方法除外其中一些不是用 Ruby 编写的,而是用与编写 Ruby 解释器相同的语言编写的。

Ruby的official document (Ver.2.5.1)String#sub的部分解释:

In the block form, the current match string is passed in as a parameter, and variables such as , , $`, $&, and $' will be set appropriately.

正确。在实践中,能够并且确实设置正则匹配相关的特殊变量如$1、$2等的方法仅限于一些内置方法,包括Regexp#matchRegexp#=~Regexp#===String#=~String#subString#gsubString#scanEnumerable#all?Enumerable#grep.
提示 1:String#split 似乎总是重置 $~ nil。
提示 2:Regexp#match?String#match? 不更新 $~,因此速度更快。

这里有一个小代码片段来强调示波器的工作原理:

def sample(str, *rest, &bloc)
  str.sub(*rest, &bloc)
      # non-nil if matches
end

sample('abc', /(c)/){}  # => "c"
p     # => nil

这里,方法 sample() 中的 </code> <em> 由 <code>str.sub 在同一范围内设置。这意味着方法 sample() 将无法(简单地)在给定的块中引用 </code>。</p> <p>我指出了Ruby官方文档(Ver.2.5.1)<a href="https://ruby-doc.org/core-2.5.1/doc/regexp_rdoc.html#class-Regexp-label-3D~+operator" rel="nofollow noreferrer">section of Regular expression</a>中的声明</p> <blockquote> <p>Using <code>=~ operator with a String and Regexp the $~ global variable is set after a successful match.

具有误导性,因为

  1. $~ 是一个 预定义局部作用域 变量( 不是 全局变量),并且
  2. $~ 已设置(可能为零),无论最后一次尝试的匹配是否成功。

$~ 等变量不是全局变量这一事实可能有点令人困惑。但是,嘿,它们是有用的符号,不是吗?