关于ruby:如何将STDOUT捕获到字符串?

How can I capture STDOUT to a string?

1
2
puts"hi"
puts"bye"

我希望将stdout存储到目前为止的代码中(在本例中,hi bye存储到变量say'result'并打印它)

1
puts result

我这样做的原因是我已经在我的Ruby代码中集成了一个R代码,当R代码运行时,输出被提供给stdout,但是在代码中不能访问ouput来做一些评估。抱歉,如果这让人困惑的话。所以"看跌结果"一行应该给我打个招呼再见。


一个将stdout捕获到字符串中的方便函数…

下面的方法是一个方便的通用工具,用于捕获stdout并将其作为字符串返回。(我经常在要验证打印到stdout的内容的单元测试中使用此方法。)请特别注意,使用ensure子句恢复$stdout(并避免惊讶):

1
2
3
4
5
6
7
8
def with_captured_stdout
  original_stdout = $stdout
  $stdout = StringIO.new
  yield
  $stdout.string
ensure
  $stdout = original_stdout
end

例如:

1
2
3
4
5
6
7
8
>> str = with_captured_stdout { puts"hi"; puts"bye"}
=>"hi
bye
"

>> print str
hi
bye
=> nil


将标准输出重定向到Stringio对象

您当然可以将标准输出重定向到变量。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Set up standard output as a StringIO object.
foo = StringIO.new
$stdout = foo

# Send some text to $stdout.
puts 'hi'
puts 'bye'

# Access the data written to standard output.
$stdout.string
# =>"hi
bye
"

# Send your captured output to the original output stream.
STDOUT.puts $stdout.string

实际上,这可能不是一个好主意,但至少现在你知道这是可能的。


如果项目中有可用的ActiveSupport,您可以执行以下操作:

1
2
3
output = capture(:stdout) do
  run_arbitrary_code
end

有关Kernel.capture的更多信息,请访问此处。


您可以通过在反勾号内调用R脚本来实现这一点,如下所示:

1
2
result = `./run-your-script`
puts result  # will contain STDOUT from run-your-script

有关在Ruby中运行子进程的详细信息,请查看此堆栈溢出问题。


出于最实际的目的,您可以将响应writeflushsyncsync=tty?的任何内容放入$stdout中。

在这个示例中,我使用来自stdlib的修改后的队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Captor < Queue

  alias_method :write, :push

  def method_missing(meth, *args)
    false
  end

  def respond_to_missing?(*args)
    true
  end
end

stream = Captor.new
orig_stdout = $stdout
$stdout = stream

puts_thread = Thread.new do
  loop do
    puts Time.now
    sleep 0.5
  end
end

5.times do
  STDOUT.print">> #{stream.shift}"
end

puts_thread.kill
$stdout = orig_stdout

如果您希望积极地对数据采取行动,而不仅仅是在任务完成后查看数据,那么就需要这样的东西。如果多个线程试图同时同步读写,那么使用Stringio或文件会有问题。


对于RinRuby,请知道R有capture.output

1
2
3
4
5
R.eval <<EOF
captured <- capture.output( ... )
EOF


puts R.captured


Minitest版本:

assert_output如果需要确保是否产生某些输出:

1
2
3
4
assert_output"Registrars processed: 1
"
do
  puts 'Registrars processed: 1'
end

断言输出

或者,如果您真的需要捕获它,可以使用capture_io

1
2
3
4
5
6
7
out, err = capture_io do
  puts"Some info"
  warn"You did a bad thing"
end

assert_match %r%info%, out
assert_match %r%bad%, err

捕获器

Minitest本身可以在任何Ruby版本中使用,从1.9.3开始。


归功于@girasquid的回答。我将其修改为单个文件版本:

1
2
3
4
5
6
7
8
def capture_output(string)
  `echo #{string.inspect}`.chomp
end

# example usage
response_body ="https:\\x2F\\x2Faccounts.google.com\\x2Faccounts"
puts response_body #=> https:\x2F\x2Faccounts.google.com\x2Faccounts
capture_output(response_body) #=> https://accounts.google.com/accounts