tags:

views:

144

answers:

6

I have a problem.

I want to run a ruby script from another ruby script and capture it's output information while letting it output to the screen too.

runner

#!/usr/bin/env ruby
print "Enter your password: "
password = gets.chomp
puts "Here is your password: #{password}"

The script file that I run:

start.rb

output = `runner`
puts output.match(/Here is your (password: .*)/).captures[0].to_s

As you see here there is a problem.

In the first line of start.rb the screen is empty.

I cannot see the "Enter your password: " in runner.

Is there a way to display the output of the runner script before it's finished, and still let me capture it to a string so I can process the information, eg. using match like in this example?

A: 

You could use tee to write the contents to a file or a pipe, and read the file afterwards.

Sjoerd
A: 

Have a look at POpen4. It claims to be platform independent (but I do not think it works in jruby where you can use IO#popen instead).

ormuriauga
I think you have misunderstood my question.
never_had_a_name
I'm not so sure. It seems you like to interact with a shell command from Ruby and POpen4 lets you do that. You can also take a look at http://ruby-doc.org/core/classes/Open3.html
Jonas Elfström
No, instead of `system "my_app create"` you call `POpen.popen("my_app create") do |stdout, sterr, stdin, pid| #code end` where `code` can read the standard out of the process you started and display it for your user and modify it as you see fit on the way. to forward what the user types you write to stdin of the process.
ormuriauga
popenN is usually the best solution, but in this case I am not so sure how would it behave with a command that expects a tty (there is a password being asked)
tokland
A: 

Have your script do its prompt output to stderr.

echo "Enter something" >&2
read answer
echo "output that will be captured"

This will be done for you if you use read -p to issue the prompt:

read -p "Enter something" answer
echo "output that will be captured"
Dennis Williamson
+1  A: 

Try this:

rd, wr = IO::pipe
pid = Process.fork do  
  $stdout.reopen(wr)
  rd.close
  exec("command")
end
wr.close
rd.each do |line|  
  puts "line from command: #{line}"
end
Process.wait(pid)

Similar if you want to capture stderr. If you need to capture both it would a bit more difficult (Kernel.select?)

Edit: Some explanation. This is an ancient Unix procedure: pipe + fork + calls to dup2 (reopen) depending on what you want. In a nutshell: you create a pipe as a means of communication between child and parent. After the fork, each peer close the pipe's endpoint it does not use, the child remaps (reopen) the channel you need to the write endpoint of the pipe and finally the parent reads on the read channel of the pipe.

tokland
It worked like a charm! Could you explain to me what each line is doing? I read the api for IO#pipe but I don't get much.
never_had_a_name
http://unixwiz.net/techtips/remap-pipe-fds.html
tokland
answer edited to add more info. related examples: http://www6.uniovi.es/cscene/CS4/CS4-06.html
tokland
@tokland. Damn, I was wrong. It didn't work. Look at my updated question.
never_had_a_name
A: 
io = IO.popen(<your command here>)
log = io.readlines
io.close

Now in log variable you have the output of executed command. Parse it, convert it, or do whatever you want.

Dmitry Maksimov
Doesn't work because it doesn't display username: in step 2. It blank and I have to type in an username then press enter for it to show password.
never_had_a_name
+1  A: 

runner.rb

STDOUT.print "Enter your password: "
password = gets.chomp
puts "Here is your password: #{password}"

Note STDOUT.print

start.rb

require "stringio"

buffer = StringIO.new
$stdout = buffer

require "runner"

$stdout = STDOUT
buffer.rewind

puts buffer.read.match(/Here is your (password: .*)/).captures[0].to_s

output

Enter your password: hello
password: hello

Read more...

I recently did a write-up on this here: Output Buffering with Ruby

macek