在方法定义中使用$ 1,$ 2等全局变量

给出以下两段代码:

def hello(z) "hello".gsub(/(o)/, &z) end z = proc {|m| p $1} hello(z) # prints: nil 

 def hello z = proc {|m| p $1} "hello".gsub(/(o)/, &z) end hello # prints: "o" 

为什么这两段代码的输出不同? 有没有办法从方法定义外部将块传递给gsub ,以便以与在方法定义中给出块的方式相同的方式计算变量$1$2

为什么输出不同?

ruby中的proc具有词法范围。 这意味着当它找到一个未定义的变量时,它会在上下文中解析proc,而不是调用 proc。 这解释了代码的行为。

您可以看到在regexp之前定义了块,这可能会导致混淆。 这个问题涉及一个魔法ruby变量,它与其他变量的工作方式完全不同。 引用@JörgWMittag

它真的很简单:$ SAFE的行为与你对全局变量的预期不同是因为它不是一个全局变量。 这是一个神奇的独角兽thingamajiggy。

Ruby中有很多神奇的独角兽故事,遗憾的是它们没有很好的文档记录(事实上并没有完全记录),因为替代Ruby实现的开发人员发现了困难的方法。 这些东西都表现得不同,而且(看似)不一致,而且它们共同的唯一两件事就是它们看起来像全局变量,但不像它们那样。

有些人有本地范围。 有些具有线程局部范围。 有些神奇地改变,没有人分配给他们。 有些人对翻译有神奇的意义,并改变语言的行为方式。 有些人还附加了其他奇怪的语义。

如果你真的想要找到$1$2变量的确切工作方式,我认为你会发现唯一的“文档”是rubyspec ,这是rubin人员用硬盘方式完成的ruby规范。 有一个很好的黑客,但要为痛苦做好准备。


有没有办法将$ 1,$ 2变量设置正确的方式从另一个上下文传递块到gsub?

你可以通过以下修改实现你想要的东西(但我打赌你已经知道了)

 require 'pp' def hello(z) #z = proc {|m| pp $1} "hello".gsub(/(o)/, &z) end z = proc {|m| pp m} hello(z) 

我不知道有办法在飞行中改变过程的范围。 但你真的想这样做吗?

$1$2这样的东西就像LOCAL VARIABLES一样 ,尽管它领先于$ 。 您可以尝试以下代码来certificate这一点:

 def foo /(hell)o/ =~ 'hello' $1 end def bar $1 end foo #=> "hell" bar #=> nil 

你的问题是因为proc z是在方法hello之外定义的,所以zmain的上下文中访问$1 ,但是gsub在方法hello的上下文中设置$1

这两个版本是不同的,因为$1变量是线程本地的和方法本地的。 在第一个示例中, $1仅存在于hello方法之外的块中。 在第二个示例中, hello方法中存在$1

无法从方法定义之外将块中的$ 1传递给gsub。

请注意, gsub将匹配字符串传递给块,因此z = proc { |m| pp m } z = proc { |m| pp m }只有在正则表达式只包含整个匹配时才会起作用。 一旦你的正则表达式包含你想要的引用以外的任何东西,你就运气不好。

例如, "hello".gsub(/l(o)/) { |m| m } "hello".gsub(/l(o)/) { |m| m } => hello ,因为整个匹配字符串已传递给块。

然而, "hello".gsub(/l(o)/) { |m| $1 } "hello".gsub(/l(o)/) { |m| $1 } => helo ,因为匹配的l被块丢弃,我们感兴趣的是捕获的o

我的解决方案是match正则表达式,然后将MatchData对象传递给块:

 require 'pp' def hello(z) string = "hello" regex = /(o)/ m = string.match(regex) string.gsub(regex, z.call(m)) end z = proc { |m| pp m[1] } pp hello(z) 

这是一个解决方法(Ruby 2)。 给定的Proc z行为与String#gsub的块完全相同。

 def hello(z) "hello".match /(o)/ # Sets $1, $2, ... z.binding.tap do |b| b.local_variable_set(:_, $~) b.eval("$~=_") end "hello".gsub(/(o)/, &z) end z = proc {|m| p $1} hello(z) # prints: "o" 

在回答 “如何将Regexp.last_match传递给Ruby中的块”(2018年发布)的问题的答案中详细解释了背景知识。