2

编辑:不要打扰阅读这个问题,我只是无法删除它。它基于损坏的代码,这里(几乎)没有什么可学的。

我在我的 Ruby 程序中重定向控制台输出,虽然它运行良好,但我很好奇一件事:

这是我的代码

capture = StringIO.new
$stdout = capture
puts "Hello World"

看起来即使我将我的capture对象分配给$stdout$stdout在分配之后包含一个新的和不同的对象,但至少类型是正确的。

换句话说:

$stdout.to_s              # => #<IO:0x2584b30>

capture = StringIO.new
$stdout = capture

$stdout.to_s              # => #<StringIO:0x4fda948>
capture.to_s              # => #<StringIO:0x4e3b220>

随后$stdout.string包含"Hello World",但capture.string为空。

幕后发生了什么事情还是我在这里遗漏了什么?

编辑:这可能仅特定于某些版本。我在 Windows 8.1 上使用 Ruby 2.0.0-p247

4

3 回答 3

2

它按预期工作。

>> capture = StringIO.new
=> #<StringIO:0x00000001ea8c00>
>> $stdout = capture
>> $stdout.to_s
>> capture.to_s 

以上两行不打印任何内容,因为$stdout现在已与终端断开连接。

所以我$stderr.puts在以下几行中使用(也可以STDOUT.puts像 Stefan 评论的那样使用):

>> $stderr.puts $stdout.to_s
#<StringIO:0x00000001ea8c00>
>> $stderr.puts capture.to_s
#<StringIO:0x00000001ea8c00>

$stdout.to_scapture.to_s给我同样的结果。

我使用红宝石 1.9.3。(与 2.0.0 相同)

于 2013-10-31T11:16:34.827 回答
1

您确定在这两者之间没有其他操纵$stdoutcapture发生吗?

对我来说,输出看起来不同。两者capture$stdout都是同一个对象,随后以string相同的响应回答(ruby 1.9.2):

require 'stringio'                                                                                                                             
$stdout.to_s              # => #<IO:0x2584b30>                                                                                                 

capture = StringIO.new                                                                                                                         
$stdout = capture                                                                                                                              

puts $stdout.to_s              # => #<StringIO:0x89a38c0>                                                                                      
puts capture.to_s              # => #<StringIO:0x89a38c0>                                                                                      
puts "redirected"

$stderr.puts $stdout.string # => '#<StringIO:0x89a38c0>\n#<StringIO:0x89a38c0>\nredirected'                                                                           
$stderr.puts capture.string # => '#<StringIO:0x89a38c0>\n#<StringIO:0x89a38c0>\nredirected'
于 2013-10-31T11:25:11.173 回答
1

尽管这个问题是由于忽略了 的值的变化而导致的$stdout,但 Ruby 确实有能力以这种方式覆盖对全局变量的赋值,至少在 C api 中,使用挂钩变量

$stdout实际上确实使用它来检查新值是否合适(它检查新值是否响应write),如果不合适则引发异常。

如果您真的想要(您不想要),您可以创建一个扩展来定义一个全局变量,该变量自动存储与分配的值不同的对象,也许通过调用dup它并使用它来代替:

#include "ruby.h"

VALUE foo;

static void foo_setter(VALUE val, ID id, VALUE *var){
  VALUE dup_val = rb_funcall(val, rb_intern("dup"), 0);
  *var = dup_val;
}

void Init_hooked() {
  rb_define_hooked_variable("$foo", &foo, 0, foo_setter);
}

然后你可以像这样使用它:

2.0.0-p247 :001 > require './ext/hooked'
 => true 
2.0.0-p247 :002 > s = Object.new
 => #<Object:0x00000100b20560> 
2.0.0-p247 :003 > $foo = s
 => #<Object:0x00000100b20560> 
2.0.0-p247 :004 > s.to_s
 => "#<Object:0x00000100b20560>" 
2.0.0-p247 :005 > $foo.to_s
 => "#<Object:0x00000100b3bea0>" 
2.0.0-p247 :006 > s == $foo
 => false 

当然,这与简单地在一个类中创建一个 setter 方法非常相似,该方法是dupvale 并存储它,您可以在纯 Ruby 中执行此操作:

def foo=(new_foo)
  @foo = new_foo.dup
end

由于使用全局变量通常是不好的设计,因此在 Ruby 中这对于全局变量是不可能的,这似乎是合理的。

于 2013-10-31T12:46:07.437 回答