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.

fearless_fool
  • 33,645
  • 23
  • 135
  • 217
script_kiddie
  • 1,167
  • 4
  • 12
  • 24
  • 3
    If you just want to get the stdout from external program, use result=%x{command}. Otherwise you can redirect stdio like showed by @codegnome – SwiftMango Feb 20 '13 at 19:28
  • Are you using `rinruby`? I also tried to catch rinruby (R) output, but up to now without success. – knut Feb 20 '13 at 19:50
  • possible duplicate of [How do I temporarily redirect stderr in Ruby?](http://stackoverflow.com/questions/4459330/how-do-i-temporarily-redirect-stderr-in-ruby) – Ciro Santilli OurBigBook.com Oct 17 '14 at 13:33
  • It's a nit, but http://stackoverflow.com/questions/4459330/how-do-i-temporarily-redirect-stderr-in-ruby 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". – fearless_fool Oct 30 '14 at 04:48

9 Answers9

84

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
fearless_fool
  • 33,645
  • 23
  • 135
  • 217
  • 2
    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. – David Moles Oct 08 '15 at 18:38
  • 3
    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`) – Brian Underwood Nov 29 '15 at 10:46
  • @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. – fearless_fool Nov 30 '15 at 18:48
  • 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 them – Brian Underwood Dec 01 '15 at 08:22
  • 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. – Jeff Nov 09 '17 at 18:39
  • 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`. – fearless_fool Nov 09 '17 at 21:43
  • Brilliant! One remark: I would use `original_stdout`, instead of `old_stdout` – Artur INTECH Sep 06 '18 at 22:10
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.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • 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 .. – script_kiddie Feb 20 '13 at 20:06
  • 1
    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. – Todd A. Jacobs Feb 20 '13 at 20:11
  • 1
    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. – Joshua Pinter Mar 15 '20 at 02:19
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

Dimitris Zorbas
  • 5,187
  • 3
  • 27
  • 33
  • 8
    ActiveSupport’s Kernel#capture is deprecated and will be removed in some future release, because it’s not thread safe. See [code](https://github.com/rails/rails/blob/e595d91ac2c07371b441f8b04781e7c03ac44135/activesupport/lib/active_support/core_ext/kernel/reporting.rb#L90) and [discussion](https://github.com/rails/rails/pull/13392). – Jakub Jirutka Dec 26 '14 at 21:18
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.

Community
  • 1
  • 1
girasquid
  • 15,121
  • 2
  • 48
  • 58
2

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.

hagello
  • 2,843
  • 2
  • 27
  • 37
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.

Kimmo Lehto
  • 5,910
  • 1
  • 23
  • 32
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

Artur INTECH
  • 6,024
  • 2
  • 37
  • 34
0

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

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

puts R.captured 
-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
Jing Li
  • 14,547
  • 7
  • 57
  • 69