15

Similar to Getting output of system() calls in Ruby , I am running a system command, but in this case I need to output the STDOUT from the command as it runs.

Community
  • 1
  • 1
Montana Harkin
  • 399
  • 5
  • 13
  • It's curious that you should have to do anything special. In my experience, when I run a program using `system`, the child program's stdout goes to the parent program's stdout, which seems to be what you want. – Wayne Conrad Nov 29 '10 at 19:47
  • This may also work: http://stackoverflow.com/questions/2215455/printing-to-screen-in-a-rake-task/2215986#2215986 – Montana Harkin Dec 06 '10 at 17:19

4 Answers4

16

As in the linked question, the answer is again not to use system at all as system does not support this.

However this time the solution isn't to use backticks, but IO.popen, which returns an IO object that you can use to read the input as it is being generated.

sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • 9
    Thanks, I ended up using `IO.popen('command_to_run') { |io| while (line = io.gets) do puts line end }` – Montana Harkin Nov 29 '10 at 17:14
  • Quick question, why is the use `backticks` not the solution or recommended, it seems? We're trying to achieve a similar thing of capturing output from a `rails notes` call. Thanks! – Joshua Pinter Aug 25 '22 at 15:34
  • 1
    @JoshuaPinter Backticks give you the output of the program as a string after it is done running. OP wanted to be able to access the program's output while it's still running. A string does not offer such functionality. `popen` gives you an `IO` object, which allows you to process the output as it arrives. – sepp2k Aug 25 '22 at 15:37
3

In case someone might want to read stdout and stderr:
It is important to read them in parallel, not first one then the other. Because programs are allowed to output to stdout and stderr by turns and even in parallel. So, you need threads. This fact isn't even Ruby-specific.

Stolen from here.

require 'open3'

cmd = './packer_mock.sh'
data = {:out => [], :err => []}

# see: http://stackoverflow.com/a/1162850/83386
Open3.popen3(cmd) do |stdin, stdout, stderr, thread|
  # read each stream from a new thread
  { :out => stdout, :err => stderr }.each do |key, stream|
    Thread.new do
      until (raw_line = stream.gets).nil? do
        parsed_line = Hash[:timestamp => Time.now, :line => "#{raw_line}"]
        # append new lines
        data[key].push parsed_line

        puts "#{key}: #{parsed_line}"
      end
    end
  end

  thread.join # don't exit until the external process is done
end
java.is.for.desktop
  • 10,748
  • 12
  • 69
  • 103
0

here is my solution

def io2stream(shell, &block)
  Open3.popen3(shell) do |_, stdout, stderr|
    while line = stdout.gets
      block.call(line)
    end

    while line = stderr.gets
      block.call(line)
    end
  end
end

io2stream("ls -la", &lambda { |str| puts str })
Xuanyu
  • 661
  • 5
  • 12
  • This would work only for some programs. One should read stdout and stderr in two parallel threads, because programs are allowed to write to those outputs in parallel. – java.is.for.desktop Mar 20 '21 at 10:13
0

With following you can capture stdout of a system command:

output = capture(:stdout) do
  system("pwd") # your system command goes here
end

puts output

shortened version:

output = capture(:stdout) { system("pwd") }

Similarly we can also capture standard errors too with :stderr

capture method is provided by active_support/core_ext/kernel/reporting.rb

Looking at that library's code comments, capture is going to be deprecated, so not sure what is the current supported method name is.

  • Looks like it has been moved to `ActiveSupport::Testing`: https://github.com/rails/rails/blob/main/activesupport/lib/active_support/testing/stream.rb – fiedl Jan 04 '22 at 22:07