2009-05-16 15 views
3

Ich habe ein Skript, das FFmpeg auf meinem lokalen Rechner imitieren soll, indem Sie den Befehl an einen Remote-Rechner senden, es dort ausführen und dann die Ergebnisse zurückgeben. (siehe previous stackoverflow question.)Wie zeige ich Fortschrittsbalken von einem Shell-Befehl über ssh

#!/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 

jetzt mein Problem ist bei

ssh.exec!(cmd) 

ich die cmd der Ausgabe an dem lokalen Benutzer in Echtzeit angezeigt werden soll. Aber macht es

puts ssh.exec!(cmd) 

Ich werde nur die resultierende Ausgabe nach dem Befehl beendet hat ausgeführt wird. Wie müsste ich den Code ändern, damit dies funktioniert?

Antwort

4

Von 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 sieht es aus wie Sie mithilfe #open_channel

Hier einige Beispiel-Code interaktiv bekommen:

[email protected]% 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" 

Und auf dem lokalen Computer:

[email protected]% 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 
[email protected]% ./client.rb 
[:data, "hello\n"] 
[:data, "earn\n"] 
[:data, "more\n"] 
[:data, "sessions\n"] 
[:data, "by\n"] 
[:data, "sleaving\n"] 
[:data, "goodbye\n"] 

So können Sie auf diese Weise mit einem laufenden Prozess interagieren.

Es ist wichtig, dass der laufende Prozess seine Ausgabe vor dem Anfordern der Eingabe spült - andernfalls könnte das Programm hängen bleiben, da der Kanal möglicherweise die unflushed Ausgabe nicht empfangen hat.

+0

ein Fortschrittsbalken würde immer noch nicht korrekt angezeigt werden? Recht? – Stefan

7

Auf der Anzeigeseite Ihrer Frage können Sie eine Aktualisierungsfortschrittsleiste in Ruby mit dem Zeichenkettenzeichen "\ r" generieren. Dies sichert Sie bis zum Anfang der aktuellen Zeile, so dass Sie sie neu schreiben können. Zum Beispiel:

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

Oder wenn Sie wollen einfach nur einen Fortschrittsbalken über den Bildschirm Sie einfach so etwas wie dies tun können:

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

Auch in Bezug auf stdout, zusätzlich Leistung pro vorherigen zu Spülung Beispiel (STDOUT.flush), können Sie Rubin stellen automatisch synchron schreibt zu einem IO-Puffer (in diesem Fall STDOUT) mit zugehörigem Gerät schreibt (im Grunde interne Pufferung wird ausgeschaltet):

STDOUT.sync = true 

Ich finde auch, dass manchmal Flush funktioniert nicht für mich, und ich verwende stattdessen "IO.fsync". Für mich ist das meistens mit der Dateisystemarbeit verbunden, aber es ist es wert zu wissen.