这是根据问题(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 $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 等是not重置,而在String#sub
、 $1 等总是重置,无论是否给出块。
另外,调用者的局部变量_
在此算法中被重置。在 Ruby 的约定中,局部变量_
用作虚拟变量,不应读取或引用其值。因此,这应该不会造成任何实际问题。如果声明local_variable_set(:$~, $~)
是有效的,不需要临时局部变量。然而,在 Ruby 中却并非如此(至少从版本 2.5.1 开始)。请参阅 Kazuhiro NISHIYAMA 的评论(日语)[红宝石列表:50708] http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/50708.
一般背景(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
is 遗传,也是如此$1
.
The do
块是yield-ed by 1.times
,以及方法1.times
除了块参数之外,无法控制块内的变量(i
在上面的例子中;n.b., 虽然Integer#times
不提供任何块参数,尝试接收块中的参数将被默默忽略)。
这意味着一种方法yield-s 块无法控制$1
, $2
等在块中,它们是局部变量(即使它们可能看起来像全局变量)。
字符串#sub 的情况
现在我们就来分析一下如何String#sub
与块作品:
'abc'.sub(/.(.)./){ |m| $1 }
这里,方法sub
首先执行正则表达式匹配,因此局部变量如$1
会自动设置。然后,他们(变量如$1
) 在块中继承,因为这个块与方法“sub”处于相同的范围内。他们是不及格 from sub
到块,与块参数不同m
(这是一个匹配的字符串,或相当于$&
).
因此,如果该方法sub
被定义在一个范围不同从块中,sub
方法无法控制块内的局部变量,包括$1
. A 范围不同是指以下情况:sub
方法是使用 Ruby 代码编写和定义的,或者实际上,所有 Ruby 方法(除了一些不是用 Ruby 编写但使用与编写 Ruby 解释器相同的语言编写的方法)。
Ruby's 官方文档(Ver.2.5.1) https://ruby-doc.org/core-2.5.1/String.html#method-i-sub在 部分中进行了解释String#sub
:
在块形式中,当前匹配字符串作为参数传入,$1、$2、$`、$& 和 $' 等变量将被适当设置。
正确的。在实践中,可以并且确实设置与正则表达式匹配相关的特殊变量(例如 $1、$2 等)的方法仅限于某些内置方法,包括Regexp#match
, Regexp#=~
, Regexp#===
,String#=~
, String#sub
, String#gsub
, String#scan
, Enumerable#all?
, and Enumerable#grep
.
Tip 1: String#split
似乎重置$~
始终为零。
Tip 2: Regexp#match?
and String#match?
不更新$~
因此速度要快得多。
下面是一个小代码片段,用于强调作用域的工作原理:
def sample(str, *rest, &bloc)
str.sub(*rest, &bloc)
$1 # non-nil if matches
end
sample('abc', /(c)/){} # => "c"
p $1 # => nil
Here, $1
在方法示例()中由设置str.sub
在同一范围内。这意味着该方法sample()
将无法(简单地)参考$1
在给它的块中。
我指出了正则表达式部分 https://ruby-doc.org/core-2.5.1/doc/regexp_rdoc.html#class-Regexp-label-3D~+operatorRuby官方文档(Ver.2.5.1)
Using =~
带有字符串和正则表达式的运算符$~
全局变量在成功匹配后设置。
相当具有误导性,因为
-
$~
is a 预定义局部范围多变的 (not全局变量),以及
-
$~
无论最后一次尝试的匹配是否成功,都会被设置(可能为零)。
事实上变量就像$~
and $1
不是全局变量可能会有点令人困惑。但是,嘿,它们是有用的符号,不是吗?