Nathan Long
Nathan Long

Reputation: 126072

Can I get continuous output from system calls in Ruby?

When you use a system call in a Ruby script, you can get the output of that command like this:

output = `ls`
puts output

That's what this question was about.

But is there a way to show the continuous output of a system call? For example, if you run this secure copy command, to get a file from a server over SSH:

scp user@someserver:remoteFile /some/local/folder/

... it shows continuous output with the progress of the download. But this:

output = `scp user@someserver:remoteFile /some/local/folder/`
puts output

... doesn't capture that output.

How can I show the ongoing progress of the download from inside my Ruby script?

Upvotes: 6

Views: 2217

Answers (5)

tokland
tokland

Reputation: 67900

Try:

IO.popen("scp -v user@server:remoteFile /local/folder/").each do |fd|
  puts(fd.readline)
end

Upvotes: 9

Nathan Long
Nathan Long

Reputation: 126072

Tokland answered the question as I asked it, but Adam's approach was what I ended up using. Here was my completed script, which does show a running count of bytes downloaded, and also a percentage complete.

require 'rubygems'
require 'net/scp'
puts "Fetching file"

# Establish the SSH session
ssh = Net::SSH.start("IP Address", "username on server", :password => "user's password on server", :port => 12345)

# Use that session to generate an SCP object
scp = ssh.scp

# Download the file and run the code block each time a new chuck of data is received
scp.download!("path/to/file/on/server/fileName", "/Users/me/Desktop/") do |ch, name, received, total|

  # Calculate percentage complete and format as a two-digit percentage
  percentage = format('%.2f', received.to_f / total.to_f * 100) + '%'

  # Print on top of (replace) the same line in the terminal
  # - Pad with spaces to make sure nothing remains from the previous output
  # - Add a carriage return without a line feed so the line doesn't move down
  print "Saving to #{name}: Received #{received} of #{total} bytes" + " (#{percentage})               \r"

  # Print the output immediately - don't wait until the buffer fills up
  STDOUT.flush
end

puts "Fetch complete!"

Upvotes: 2

Dennis Williamson
Dennis Williamson

Reputation: 360485

Redirecting stderr to stdout may work for you:

output = `scp user@someserver:remoteFile /some/local/folder/ 2>&1`
puts output

That should capture both stderr and stdout. You can capture stderr only by throwing away stdout:

output = `scp user@someserver:remoteFile /some/local/folder/ 2>&1 >/dev/null`
puts output

You can then use IO.popen.

Upvotes: 0

Adam
Adam

Reputation: 821

I think you would have better luck using the ruby standard library to handle SCP (as opposed to forking a shell process). The Net::SCP library (as well as the entire Net::* libraries) are full featured and used with Capistrano to handle remote commands.

Checkout http://net-ssh.rubyforge.org/ for a rundown of what is available.

Upvotes: 3

ALoR
ALoR

Reputation: 4914

have you tried with IO.popen ? you should be able to read the output while the process is still running and parse it accordingly.

Upvotes: 0

Related Questions