Ruby命令行隐式条件检查

时间:2015-06-03 22:15:26

标签: ruby shell

我从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/'

不会输出任何内容。

2 个答案:

答案 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 的特殊情况。通常(即不使用enp命令行选项)此代码:

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条件的常规规则,这是一个(记录不完整的)例外,其中任何非falsenil的评估都是真实的。

这仅适用于正则表达式文字,以下内容的行为与您对条件的预期相同:

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社区的青睐。