ruby - 如何将Regexp.last_match传递给Ruby中的 block

标签 ruby regex scope iterator yield

有没有办法把最后一个匹配(实际上是Regexp.last_match)传递给ruby中的块(迭代器)?
这里有一个示例方法作为Srring#sub的包装器来演示这个问题。它接受标准参数和块:

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

它只在标准参数的情况下工作,并且可以接受一个块;但是像$1、$2等位置特殊变量在块中不可用。下面是一些例子:
newsub("abcd", /ab(c)/, '\1')        # => "cd"
newsub("abcd", /ab(c)/){|m| $1}      # => "d"  ($1 == nil)
newsub("abcd", /ab(c)/){$1.upcase}   # => NoMethodError

块不能以与String#sub(/..(.)/){$1}相同的方式工作的原因是我认为与作用域有关;特殊变量$1,$2等是局部变量(也是Regexp.last_match)。
有什么办法解决这个问题吗?我想让方法newsubString#sub一样工作,从这个意义上说,$1,$2等在提供的块中是可用的。
编辑:根据some past answers,可能没有办法实现这一点……

最佳答案

下面是一个关于问题的方法(ruby 2)。它不是很漂亮,也不是完全完美的所有方面,但做的工作。

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 $1  # => "xyz"
p _   # => 0

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

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

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

深入分析
在上述定义的方法newsub中,当给定块时,调用方线程中的局部变量$1等在执行块后被(重新)设置,这与String#sub一致。然而,当没有给定块时,局部变量$1 etc不会重置,而在String#sub中,$1 etc总是重置,无论是否给定块。
此外,调用方的局部变量_在该算法中被重置。在ruby的约定中,局部变量_被用作伪变量,其值不应被读取或引用。因此,这不应造成任何实际问题。如果语句local_variable_set(:$~, $~)有效,则不需要临时局部变量。然而,在ruby中(至少从2.5.1版开始)不是这样。在[ruby-list:50708]中可以看到西山由纪郎(kazuhiro nishiyama)的评论(日语)。
解释了一般背景(ruby规范)
下面是一个简单的例子来强调与这个问题相关的ruby规范:
s = "abcd"
/b(c)/ =~ s
p $1     # => "c"
1.times do |i|
  p s    # => "abcd"
  p $1   # => "c"
end

$&$1$2等特殊变量(相关,$~Regexp.last_match),$'等)
在本地范围内工作。在ruby中,本地作用域继承父作用域中同名的变量。
在上面的例子中,变量s是继承的,$1也是继承的。
do块由1.times产生,并且方法1.times除了块参数之外,对块内的变量没有控制(i在上面的示例中;注意,尽管Integer#times没有提供任何块参数,但是尝试接收块中的一个(s)将被忽略)。
这意味着生成块的方法不能控制块中的$1$2等,这些都是局部变量(即使它们看起来像全局变量)。
字符串sub的大小写
现在,让我们分析一下String#sub块是如何工作的:
'abc'.sub(/.(.)./){ |m| $1 }

这里,方法sub首先执行regexp匹配,因此会自动设置像$1这样的局部变量。然后,它们(变量如$1)在块中继承,因为这个块与方法“sub”在同一范围内。它们不会从sub传递到块,这与块参数m不同(块参数是匹配的字符串,或等效于$&)。
因此,如果方法sub是在与块不同的范围内定义的,sub方法不能控制块内的局部变量,包括$1。不同的范围意味着sub方法是用ruby代码编写和定义的,或者实际上,所有的ruby方法,除了一些不是用ruby编写的,而是用与编写ruby解释器相同的语言编写的方法。
ruby'sofficial document (Ver.2.5.1)String#sub一节中解释:
在块格式中,当前匹配字符串作为参数传入,并将适当设置诸如$1、$2、$`、$&和$'等变量。
对的。实际上,可以和确实设置regexp匹配相关特殊变量(如$1、$2等)的方法仅限于一些内置方法,包括Regexp#matchRegexp#=~Regexp#===String#=~String#subString#gsubString#scanEnumerable#all?Enumerable#grepString#split$~
提示1:Regexp#match?似乎总是重置为零。
提示2:String#match?$~不更新$1,因此速度更快。
下面是一个小代码片段,重点介绍作用域的工作原理:
def sample(str, *rest, &bloc)
  str.sub(*rest, &bloc)
  $1    # non-nil if matches
end

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

这里,sample()方法中的str.subsample()在同一范围内设置。这意味着$1方法不能(简单地)在给定的块中引用=~
我指出了ruby官方文档(2.5.1版)中的section of Regular expression语句。
对字符串和regexp使用$~运算符,成功匹配后设置$~全局变量。
有点误导,因为
$~是预定义的局部范围变量(不是全局变量),并且
$~被设置(可能为零),而不管上次尝试的匹配是否成功。
$1这样的变量不是全局变量这一事实可能会让人有点困惑。但是嘿,它们是有用的符号,不是吗?

关于ruby - 如何将Regexp.last_match传递给Ruby中的 block ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52359278/

相关文章:

Ruby 运算符优先级表

javascript - 使用 ngRepeat 时限制显示结果的数量

javascript - 如何在 RegExp 中创建 OR 条件?

javascript - 字符串中冒号的不区分大小写的正则表达式

regex - 如何使用记事本+删除单列中文本之间的空格

c++ - 需要有关如何在 C++ 中存储内容的想法

C++ 多个具有相同名称的类

ruby - 第一次获取请求后,Ruby Mechanize 的速度如何?

ruby-on-rails - rails : Many to many relation to self

ruby - 挽救多个错误