我从bash shell运行了以下命令:
echo 'hello world' | ruby -ne 'puts $_ if /hello/'
我认为这首先是一个错字,但它令人惊讶地输出hello world
。
我打算输入:
echo 'hello world' | ruby -ne 'puts $_ if /hello/ === $_'
任何人都可以解释或指出文档,为什么我们将这种隐式比较与$_
进行比较?
我还想注意:
echo 'hello world' | ruby -ne 'puts $_ if /test/'
不会输出任何内容。
答案 0 :(得分:4)
在深入研究Ruby源(MRI)之后,我想我找到了解释。
代码:
pp RubyVM::InstructionSequence.compile('puts "hello world" if /hello/').to_a
产生以下输出:
...
[:trace, 1],
[:putobject, /hello/],
[:getspecial, 0, 0],
[:opt_regexpmatch2],
...
指令似乎是用两个参数调用opt_regexpmatch2,第一个参数是正则表达式/hello/
,第二个参数是getspecial
的返回值
getspecial可以在insns.def
中找到/**
@c variable
@e Get value of special local variable ($~, $_, ..).
@j 特殊なローカル変数($~, $_, ...)の値を得る。
*/
DEFINE_INSN
getspecial
(rb_num_t key, rb_num_t type)
()
(VALUE val)
{
val = vm_getspecial(th, GET_LEP(), key, type);
}
请注意,我们的说明很可能会告诉VM恢复$_
的值。当我们使用正确的选项运行ruby时会自动为我们设置$_
,例如-n
现在我们有两个参数,我们称之为opt_regexpmatch2
/**
@c optimize
@e optimized regexp match 2
@j 最適化された正規表現マッチ 2
*/
DEFINE_INSN
opt_regexpmatch2
(CALL_INFO ci)
(VALUE obj2, VALUE obj1)
(VALUE val)
{
if (CLASS_OF(obj2) == rb_cString &&
BASIC_OP_UNREDEFINED_P(BOP_MATCH, STRING_REDEFINED_OP_FLAG)) {
val = rb_reg_match(obj1, obj2);
}
else {
PUSH(obj2);
PUSH(obj1);
CALL_SIMPLE_METHOD(obj2);
}
}
在一天结束时
if /hello/'
相当于if $_ =~ /hello/
- $_
将为nil
,除非我们使用正确的选项运行ruby
。
答案 1 :(得分:4)
Ruby解析器在条件语句中具有正则表达式 literals 的特殊情况。通常(即不使用e
,n
或p
命令行选项)此代码:
if /foo/
puts "TRUE!"
end
产生
$ ruby regex-in-conditional1.rb
regex-in-conditional1.rb:1: warning: regex literal in condition
首先将与正则表达式匹配的内容分配给$_
,如下所示:
$_ = 'foo'
if /foo/
puts "TRUE!"
end
产生
$ ruby regex-in-conditional2.rb
regex-in-conditional2.rb:2: warning: regex literal in condition
TRUE!
对于Ruby条件的常规规则,这是一个(记录不完整的)例外,其中任何非false
或nil
的评估都是真实的。
这仅适用于正则表达式文字,以下内容的行为与您对条件的预期相同:
regex = /foo/
if regex
puts "TRUE!"
end
输出:
$ ruby regex-in-conditional3.rb
TRUE!
这在解析器中处理。在MRI代码中搜索警告文本会产生一个match in parse.y
:
case NODE_DREGX:
case NODE_DREGX_ONCE:
warning_unless_e_option(parser, node, "regex literal in condition");
return NEW_MATCH2(node, NEW_GVAR(rb_intern("$_")));
我不知道Bison,所以我无法准确解释这里发生了什么,但是你可以推断出一些线索。如果设置了-e
选项,warning_unless_e_option
函数会简单地抑制警告,因为在正常代码中不鼓励使用此功能,但在命令行的表达式中可以使用此功能(这解释了为什么你看不到代码中的警告)。下一行似乎是构造一个解析子树,它是正则表达式与$_
全局变量之间的正则表达式匹配,其中包含“[t]he last input line of string by gets or readline”。然后将这些节点编译成通常正则表达式方法调用。
这显示了正在发生的事情,我将完成Kernel#gets
documentation的引用,这可以解释为什么这是一个如此模糊的特征
使用$ _作为隐式参数的编程风格逐渐失去了Ruby社区的青睐。