1

I am trying to execute an interactive shell program on a remote host from another ruby program. For the sake of simplicity let's suppose that the program I want to execute is something like this:

puts "Give me a number:"
number = gets.chomp()
puts "You gave me #{number}"

The approach that most successful has been so far is using the one I got from here. It is this one:

require 'open3'
Open3.popen3("ssh -tt root@remote 'ruby numbers.rb'") do |stdin, stdout, stderr|
    # stdin  = input stream
    # stdout = output stream
    # stderr = stderr stream
    threads = []
    threads << Thread.new(stderr) do |terr|
        while (line = terr.gets)
          puts "stderr: #{line}"
        end
    end
    threads << Thread.new(stdout) do |terr|
        while (line = terr.gets)
          puts "stdout: #{line}"
        end
    end

    sleep(2)
    puts "Give me an answer: "
    answer = gets.chomp()
    stdin.puts answer


    threads.each{|t| t.join()} #in order to cleanup when you're done.
end

The problem is that this is not "interactive" enough to me, and the program that I would like to execute (not the simple numbers.rb) has a lot more of input / output. You can think of it as an apt-get install that will ask you for some input to solve some problems.
I have read about net::ssh and pty, but couldn't see if they were going to be the (easy/elegant) solution I am looking for.

The ideal solution will be to make it in such a way that the user does not realize that the IO is being done on a remote host: the stdin goes to the remote host stdin, the stdout from the remote host comes to me and I show it.

If you have any ideas I could try I will be happy to hear them. Thank you!

3
  • Have you tried using Net::SSH? Commented Mar 8, 2012 at 15:47
  • I have seen the on_data method to get the output. The problem is that I don't want to execute a command and get its output. I want to execute a command, show its output, get the input from the user, show more output, etc. I don't see how to do that with net::ssh (or the others). Commented Mar 8, 2012 at 16:34
  • 1
    Re: The ideal solution will be to make it in such a way that the user does not realize that the IO is being done on a remote host: the stdin goes to the remote host stdin, the stdout from the remote host comes to me and I show it. The way to achieve that is ssh user@remote program. No local Ruby program to get in the way of the transparency. :) Commented Mar 8, 2012 at 21:06

1 Answer 1

6

Try this:

require "readline"
require 'open3'

Open3.popen3("ssh -tt root@remote 'ruby numbers.rb'") do |i, o, e, th|


  Thread.new {
    while !i.closed? do
      input =Readline.readline("", true).strip 
      i.puts input
    end
  }

  t_err = Thread.new {
    while !e.eof?  do
      putc e.readchar
    end
  }

  t_out = Thread.new {
    while !o.eof?  do
      putc o.readchar
    end
  }

  Process::waitpid(th.pid) rescue nil 
  # "rescue nil" is there in case process already ended.

  t_err.join
  t_out.join

end

I got it working, but don't ask me why it works. It was mainly trial/error.

Alternatives:

  • Using Net::SSH, you need to use :on_process and a Thread: ruby net/ssh channel dies? Don't forget to add session.loop(0.1). More info at the link. The Thread/:on_process idea inspired me to write a gem for my own use: https://github.com/da99/Chee/blob/master/lib/Chee.rb
  • If the last call in your Ruby program is SSH, then you can exec ssh -tt root@remote 'ruby numbers.rb'. But, if you still want interactivity between User<->Ruby<->SSH, then the previous alternative is the best.
Sign up to request clarification or add additional context in comments.

2 Comments

This is such a useful piece of code. Thanks for creating and sharing this.
This doesn't really work to the extent needed to be able to fully interact with the shell. For example, some commands may require vi(m) to open and allow a document to be edited, and this wouldn't handle that. Though it would be nice if Ruby could handle it, I think many times it is safer just to write shell scripts in bash if you will require that level of interactivity.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.