Archive for the 'Ruby' Category

output and exit code when threading remote ssh commands

There are many articles around on the various ways of getting stdout, stderr and exit codes etc from a remotely executed ssh command, but they all seem to be complicated, or they just don’t give you everything you need.

The most common situation I have come across is needing the stdout and stderr merged into ‘output’, and the exit code. The problem has always been around using `back-ticks` which seems to give me output, and system() which gives me exit code, or something more complicated. Often you also see references to using $? which returns the exit code of the last exited child process, but this is fragile if you are spawning multiple children to run things in parallel.

In the end, the simplest and least fragile approach seemed to be to wrap the command appropriately on the remove host, merge stdout and stderr, and then append the exit code to the end of the output and parse it off when I get it back.

The remote executed command for ‘date’, would basically looks like

sh -c "date"; EC=$?; sleep 1; echo $EC 2>&1

A little explanation is required.

  • sh -c wraps the whole command up, so that I can be sure to get the exit code of the whole command rather than part of it
  • Once executed, I grab the exit code and save it as EC
  • The sleep statement gives stdout and stderr time to drain
  • The exit $EC returns the exit code from the command I ran, rather than from the sleep statement.
  • 2>&1 redirects stderr to stdout.

All in all, this ensures I pick up everything from the remote host, including exit code, and i can parallize it without worring about race conditions.

This is all pretty simply wrapped in a few ruby methods

require 'net/ssh'
def extended_command cmd
   "sh -c \""+cmd+"\"; EC=$?; sleep 1; echo $EC 2>&1"
end
 
def parse output
  foo = output.to_a
  @exit_code = foo.pop.to_i
  foo.map! { |line| line.chomp }
  @output = foo.join("\n")
end
 
@host = 'blah'
@uid = 'someuser'
 
Net::SSH.start(@host, @uid ) do |ssh|
    result = ssh.exec!( "#{ extended_command "id;hostname"  }" )
end
 
parse result
 
puts @output
puts @exit_code

The commands can get reasonably complex without anything breaking, separating with ; or using pipes seems to work as expected. If you wanted to redirect output in your command, you need to escape it twice, first you have to escape the escape, then escape the redirect, i.e.

ls -al \\\> directory.list

Cheers
Pete

More useful ping output

Sometimes its useful to have smarter output from a ping command. The things I needed were

  • 1 second intervals
  • the time the ping happened
  • how many pings since the last state change
  • the status

I have needed this on several occasions when doing redundancy testing, but it has always been more pressing to do the job, rather than write little scripts, but laziness got the better of me and it was time to make this little task a bit simpler. The result is a very simple ruby script.

#!/usr/bin/ruby
 
host=ARGV[0]
 
ping = `which ping`.chomp
args = " -c 1 -t 1 #{host}"
cmd = "#{ping} #{args}"
 
puts "#{cmd}"
 
`#{cmd}`
state = $?.to_i
newstate = state
count = 0
 
next_time=Time.now+1
 
while ( newstate == state )
      count = count + 1
        time = Time.now
        puts "at #{time.min}:#{time.sec}, count since change is: #{count}, and #{host} is: " + ( state.zero? ? "Up" : "Down")
 
        `#{cmd}`
        newstate = $?.to_i
        if ( newstate != state ) then
                count = 0
                state = newstate
        end
 
        pause = next_time-Time.now
        if pause > 0 then
          sleep pause
        end
 
        next_time=Time.now+1
end

The output is quite useful now

at 14:7, count since change is: 1, and 10.1.32.1 is: Up
at 14:8, count since change is: 2, and 10.1.32.1 is: Up
at 14:9, count since change is: 3, and 10.1.32.1 is: Up
at 14:10, count since change is: 4, and 10.1.32.1 is: Up
at 14:11, count since change is: 5, and 10.1.32.1 is: Up
at 14:12, count since change is: 6, and 10.1.32.1 is: Up
at 14:13, count since change is: 7, and 10.1.32.1 is: Up

There are probably loads of little things that could be changed to make it more useful or to provide better output, throw in any suggestions.

Cheers
Pete