views:

771

answers:

2

I've got a script thats supposed to mimic ffmpeg on my local machine, by sending the command of to a remote machine, running it there and then returning the results. (see previous stack*overflow*** question.)

#!/usr/bin/env ruby

require 'rubygems'
require 'net/ssh'
require 'net/sftp'
require 'highline/import'


file = ARGV[ ARGV.index( '-i' ) + 1] if ARGV.include?( '-i' )  
puts 'No input file specified' unless file;

host = "10.0.0.10"
user = "user"
prod = "new-#{file}"               # product filename (call it <file>-new)
rpath = "/home/#{user}/.rffmpeg"   # remote computer operating directory
rfile = "#{rpath}/#{file}"         # remote filename
rprod = "#{rpath}/#{prod}"         # remote product
cmd = "ffmpeg -i #{rfile} #{rprod}"# remote command, constructed

pass = ask("Password: ") { |q| q.echo = false }  # password from stdin

Net::SSH.start(host, user ) do |ssh|
        ssh.sftp.connect do |sftp|

                # upload local 'file' to remote 'rfile'
                sftp.upload!(file, rfile)

                # run remote command 'cmd' to produce 'rprod'
                ssh.exec!(cmd)

                # download remote 'rprod' to local 'prod'
                sftp.download!(rprod, prod)
        end
end

now my problem is at

ssh.exec!(cmd)

I want to display the cmd's output to the local user in real-time. But making it

puts ssh.exec!(cmd)

I only get the resulting output after the command has finished running. How would I have to change the code to make this work?

+3  A: 

From ri Net::SSH::start:

 -------------------------------------------------------- Net::SSH::start
      Net::SSH::start(host, user, options={}, &block) {|connection| ...}
 ------------------------------------------------------------------------
      The standard means of starting a new SSH connection. When used with
      a block, the connection will be closed when the block terminates,
      otherwise the connection will just be returned. The yielded (or
      returned) value will be an instance of
      Net::SSH::Connection::Session (q.v.). (See also
      Net::SSH::Connection::Channel and Net::SSH::Service::Forward.)

        Net::SSH.start("host", "user") do |ssh|
          ssh.exec! "cp /some/file /another/location"
          hostname = ssh.exec!("hostname")

          ssh.open_channel do |ch|
            ch.exec "sudo -p 'sudo password: ' ls" do |ch, success|
              abort "could not execute sudo ls" unless success

              ch.on_data do |ch, data|
                print data
                if data =~ /sudo password: /
                  ch.send_data("password\n")
                end
              end
            end
          end

          ssh.loop
        end

So it looks like you can get more interactive by using #open_channel

Here's some example code:

user@server% cat echo.rb
#! /usr/local/bin/ruby
def putsf s
  puts s
  STDOUT.flush
end
putsf "hello"
5.times do
        putsf gets.chomp
end
putsf "goodbye"

And on your local machine:

user@local% cat client.rb
#! /usr/local/bin/ruby
require 'rubygems'
require 'net/ssh'
words = %w{ earn more sessions by sleaving }
index = 0;
Net::SSH.start('server', 'user') do |ssh|
  ssh.open_channel do |ch|
    ch.exec './echo.rb' do |ch, success|
      abort "could not execute ./echo.rb" unless success

      ch.on_data do |ch, data|
        p [:data, data]
        index %= words.size
        ch.send_data( words[index] + "\n" )
        index += 1
      end
    end
  end
end
user@local% ./client.rb
[:data, "hello\n"]
[:data, "earn\n"]
[:data, "more\n"]
[:data, "sessions\n"]
[:data, "by\n"]
[:data, "sleaving\n"]
[:data, "goodbye\n"]

So you can interact with a running process this way.

It's important that the running process flush its output before requesting input - otherwise, the program might hang as the channel may not have received the unflushed output.

rampion
a progress bar would still not be displayed correctly? right?
NixNinja
+1  A: 

On the display side of your question, you can generate an updating progress bar in Ruby using the "\r" string char. This backs you up to the beginning of the current line allowing you to re-write it. For example:

1.upto(100) { |i| sleep 0.05; print "\rPercent Complete #{i}%"}

Or if you just want a progress bar across the screen you can simply do something similar to this:

1.upto(50) { sleep 0.05; print "|"}

Also, relating to stdout, in addition to flushing output per previous example (STDOUT.flush), you can ask Ruby to automatically sync writes to an IO buffer (in this case STDOUT) with associated device writes (basically turns off internal buffering):

STDOUT.sync = true

Also, I find that sometimes flush doesn't work for me, and I use "IO.fsync" instead. For me that's mostly been related to file system work, but it's worth knowing.

science