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
Recent Comments