有没有办法把最后一个匹配(实际上是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
)。有什么办法解决这个问题吗?我想让方法
newsub
像String#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#match
、Regexp#=~
、Regexp#===
、String#=~
、String#sub
、String#gsub
、String#scan
、Enumerable#all?
、Enumerable#grep
、String#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.sub
由sample()
在同一范围内设置。这意味着$1
方法不能(简单地)在给定的块中引用=~
。我指出了ruby官方文档(2.5.1版)中的section of Regular expression语句。
对字符串和regexp使用
$~
运算符,成功匹配后设置$~
全局变量。有点误导,因为
$~
是预定义的局部范围变量(不是全局变量),并且$~
被设置(可能为零),而不管上次尝试的匹配是否成功。像
$1
和关于ruby - 如何将Regexp.last_match传递给Ruby中的 block ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52359278/