6

I would like to execute an external process in Ruby using spawn (for multiple concurrent child processes) and collect the stdout or stderr into a string, in a similar way to what can be done with Python's subprocess Popen.communicate().

I tried redirecting :out/:err to a new StringIO object, but that generates an ArgumentError, and temporarily redefining $stdxxx would mix up the outputs of the child processes.

Dologan
  • 4,554
  • 2
  • 31
  • 33

4 Answers4

7

In case you don't like popen, here's my way:

r, w = IO.pipe
pid = Process.spawn(command, :out => w, :err => [:child, :out]) 
w.close

...

pid, status = Process.wait2
output = r.read
r.close

Anyway you can't redirect to a String object directly. You can at most direct it to an IO object and then read from that, just like the code above.

Robert
  • 1,964
  • 1
  • 22
  • 22
3

Why do you need spawn? Unless you are on Windows you can use popen*, e.g. popen4:

require "open4"

pid, p_i, p_o, p_e = Open4.popen4("ls")
p_i.close
o, e = [p_o, p_e].map { |p| begin p.read ensure p.close end }
s = Process::waitpid2(pid).last
Victor Moroz
  • 9,167
  • 1
  • 19
  • 23
  • Excellent! This seems to do what I want. I was using `spawn` under the impression that popen would somehow force me wait for the child process to complete (as Python's `communicate()` does), which wouldn't work for me since I need parallel execution, but it appears that I was mistaken. `popen4` seems not to be part of the standard library, which I'd like to stick to, but `popen3` is, and does the trick just as well. Thanks! – Dologan May 02 '12 at 17:15
1

From the Ruby docs it seems that you can't, but you can do this:

spawn("ls", 0 => ["/tmp/ruby_stdout_temp", "w"])
stdoutStr=File.read("/tmp/ruby_stdout_temp")

You can also do the same with standard error. Or, if you wan't to do that and don't mind popen:

io=IO.popen("ls")
stdout=io.read
Linuxios
  • 34,849
  • 13
  • 91
  • 116
1

The most simple and straightforward way seems

require 'open3'

out, err, ps = Open3.capture3("ls")

puts "Process failed with status #{ps.exitstatus}" unless ps.success?

Here we have the outputs as strings.

TNT
  • 3,392
  • 1
  • 24
  • 27