How can I capture STDOUT to a string?
Asked Answered
V

9

51
puts "hi"
puts "bye"

I want to store the STDOUT of the code so far (in this case hi \nbye into a variable say 'result' and print it )

puts result

The reason I am doing this is I have integrate an R code into my Ruby code, output of which is given to the STDOUT as the R code runs , but the ouput cannot be accessed inside the code to do some evaluations. Sorry if this is confusing. So the "puts result" line should give me hi and bye.

Vasili answered 20/2, 2013 at 18:32 Comment(4)
If you just want to get the stdout from external program, use result=%x{command}. Otherwise you can redirect stdio like showed by @codegnomeSprint
Are you using rinruby? I also tried to catch rinruby (R) output, but up to now without success.Vulcanite
possible duplicate of How do I temporarily redirect stderr in Ruby?Pilgrimage
It's a nit, but #4459830 refers to stderr, this one to stdout. You could merge them, but you'd want a search for "capture stdout" to find an answer as well as "capture stderr".Brownedoff
B
47

Redirect Standard Output to a StringIO Object

You can certainly redirect standard output to a variable. For example:

# 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\nbye\n"

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

In practice, this is probably not a great idea, but at least now you know it's possible.

Bedsore answered 20/2, 2013 at 19:25 Comment(3)
This is exactly what i need .. but it does not work . can you just have a look at it again please ? its gives a blank output ..even the puts and hi abd byt insdei dont get printed . I have included require stringio .. THanks for the help ..Vasili
No, they won't get printed, because you've redirected standard output to your StringIO. Use STDOUT.puts $stdout.string to print to the original output stream instead.Bedsore
Honestly, this was the only thing that worked for my cases where I wanted to call Rake::Task[ "mytask" ].invoke without outputting the result to STDOUT and instead capture it for later use. I restore STDOUT immediately after using an ensure to avoid any issue. This might be dangerous if misused but it works very well when used sparingly.Sink
B
85

A handy function for capturing stdout into a string...

The following method is a handy general purpose tool to capture stdout and return it as a string. (I use this frequently in unit tests where I want to verify something printed to stdout.) Note especially the use of the ensure clause to restore $stdout (and avoid astonishment):

def with_captured_stdout
  original_stdout = $stdout  # capture previous value of $stdout
  $stdout = StringIO.new     # assign a string buffer to $stdout
  yield                      # perform the body of the user code
  $stdout.string             # return the contents of the string buffer
ensure
  $stdout = original_stdout  # restore $stdout to its previous value
end

So, for example:

>> str = with_captured_stdout { puts "hi"; puts "bye"}
=> "hi\nbye\n"
>> print str
hi
bye
=> nil
Brownedoff answered 1/4, 2014 at 5:50 Comment(7)
Note that this won't necessarily work with, e.g. Logger, if it captures a reference to the original $stdout before you have a chance to reassign it.Amanita
Exactly what I needed, thanks! FYI on a cool Ruby trick, if your begin/end match the duration of your method, you can just skip them and just put the ensure at the end of your method (generally the ensure/rescue is indented at the same level as the method's def and end)Counsellor
@BrianUnderwood: True, but just because you can do something in Ruby doesn't always mean you should :). I included the begin/end statements mostly to make it easier for newcomers to understand. Old pros like you will know they can elide the begin/end in this case.Brownedoff
I agree that there are some things you shouldn't do, but if this is a better syntax I would use it, even for beginners, because I don't think it's likely to confuse (and I love giving people an "I didn't know you could do that" moment even if that wasn't the point of the code). They say the best way to teach kids complicated words is to not shy away from themCounsellor
Does this work with external commands? e.g. should this work? with_captured_std_out { %x(wget http://google.com) } I cant get it to work in this context.Electron
If %x is like backticks, it returns a string -- it doesn't print to stdout -- so you neither need nor want to wrap it with_captured_std_out.Brownedoff
Brilliant! One remark: I would use original_stdout, instead of old_stdoutTunic
B
47

Redirect Standard Output to a StringIO Object

You can certainly redirect standard output to a variable. For example:

# 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\nbye\n"

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

In practice, this is probably not a great idea, but at least now you know it's possible.

Bedsore answered 20/2, 2013 at 19:25 Comment(3)
This is exactly what i need .. but it does not work . can you just have a look at it again please ? its gives a blank output ..even the puts and hi abd byt insdei dont get printed . I have included require stringio .. THanks for the help ..Vasili
No, they won't get printed, because you've redirected standard output to your StringIO. Use STDOUT.puts $stdout.string to print to the original output stream instead.Bedsore
Honestly, this was the only thing that worked for my cases where I wanted to call Rake::Task[ "mytask" ].invoke without outputting the result to STDOUT and instead capture it for later use. I restore STDOUT immediately after using an ensure to avoid any issue. This might be dangerous if misused but it works very well when used sparingly.Sink
B
11

If activesupport is available in your project you may do the following:

output = capture(:stdout) do
  run_arbitrary_code
end

More info about Kernel.capture can be found here

Barbbarba answered 1/7, 2014 at 9:5 Comment(1)
ActiveSupport’s Kernel#capture is deprecated and will be removed in some future release, because it’s not thread safe. See code and discussion.Liverpudlian
R
9

You can do this by making a call to your R script inside backticks, like this:

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

For more information on running subprocesses in Ruby, check out this Stack Overflow question.

Remarque answered 20/2, 2013 at 18:35 Comment(1)
.. this works but the other soln was what i was looking at .. thanks for the reply .. appreciate.Vasili
W
3

Capture stdout (or stderr) for both Ruby code and subprocesses

# capture_stream(stream) { block } -> String
#
# Captures output on +stream+ for both Ruby code and subprocesses
# 
# === Example
#
#    capture_stream($stdout) { puts 1; system("echo 2") }
# 
# produces
# 
#    "1\n2\n"
#
def capture_stream(stream)
  raise ArgumentError, 'missing block' unless block_given?
  orig_stream = stream.dup
  IO.pipe do |r, w|
    # system call dup2() replaces the file descriptor 
    stream.reopen(w) 
    # there must be only one write end of the pipe;
    # otherwise the read end does not get an EOF 
    # by the final `reopen`
    w.close 
    t = Thread.new { r.read }
    begin
      yield
    ensure
      stream.reopen orig_stream # restore file descriptor 
    end
    t.value # join and get the result of the thread
  end
end

I got inspiration from Zhon.

Wenda answered 7/8, 2019 at 12:47 Comment(0)
F
1

For most practical purposes you can put anything into $stdout that responds to write, flush, sync, sync= and tty?.

In this example I use a modified Queue from the stdlib.

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

You need something like this if you want to actively act on the data and not just look at it after the task has finished. Using StringIO or a file will have be problematic with multiple threads trying to sync reads and writes simultaneously.

Fribble answered 9/6, 2017 at 12:7 Comment(0)
T
1

Minitest versions:


assert_output if you need to ensure if some output is generated:

assert_output "Registrars processed: 1\n" do
  puts 'Registrars processed: 1'
end

assert_output


or use capture_io if you really need to capture it:

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

capture_io

Minitest itself is available in any Ruby version starting from 1.9.3

Tunic answered 2/10, 2018 at 19:49 Comment(0)
L
0

For RinRuby, please know that R has capture.output:

R.eval <<EOF
captured <- capture.output( ... )
EOF

puts R.captured 
Luann answered 5/1, 2019 at 18:24 Comment(0)
S
-2

Credit to @girasquid's answer. I modified it to a single file version:

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
Selene answered 28/9, 2016 at 10:26 Comment(1)
this is a remote code execution vulnerabilitySteer

© 2022 - 2024 — McMap. All rights reserved.