1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
|
#--
# =============================================================================
# Copyright (c) 2004,2005 Jamis Buck (jamis@37signals.com)
# All rights reserved.
#
# This source file is distributed as part of the Net::SSH Secure Shell Client
# library for Ruby. This file (and the library as a whole) may be used only as
# allowed by either the BSD license, or the Ruby license (or, by association
# with the Ruby license, the GPL). See the "doc" subdirectory of the Net::SSH
# distribution for the texts of these licenses.
# -----------------------------------------------------------------------------
# net-ssh website : http://net-ssh.rubyforge.org
# project website: http://rubyforge.org/projects/net-ssh
# =============================================================================
#++
require 'stringio'
module Net
module SSH
module Service
module Shell
# A version of Shell::Shell that acts synchronously, allowing clients
# to execute a command via a shell, wait for it to finish, and then
# inspect both the stdout and stderr from the command, as well as the
# exit status.
class SyncShell
# A struct representing the result of executing a command.
CommandOutput = Struct.new( :stdout, :stderr, :status )
# The unique confirmation string that is used to recognize the
# end of a command's execution.
CONFIRMATION = "2357foobarbazzabraboof7532"
# Create a new SyncShell that uses the given +shell+ factory to obtain
# a shell to wrap. The other parameters are used
def initialize( shell, log, pty_opts )
@log = log
@shell = shell.call( pty_opts )
end
# Delegates to Shell::Shell.open?
def open?
@shell.open?
end
# Attempts to invoke the given command, which must not be
# terminated with a newline. If +stdin+ is not nil, it will be sent
# to the shell immediately after sending the request to execute the
# command. It is expected that this will be used by the program that
# was just executed somehow, but this is not (cannot) be enforced.
def send_command( cmd, stdin=nil )
@log.debug "executing #{cmd.inspect}" if @log.debug?
send_data "#{cmd}; printf '%s %d' #{CONFIRMATION} $?\n"
send_data stdin if stdin
out = ""
err = ""
@log.debug "waiting for #{cmd.inspect}" if @log.debug?
loop do
sleep 0.01
out << @shell.stdout while @shell.open? && @shell.stdout?
err << @shell.stderr while @shell.open? && @shell.stderr?
break if !@shell.open? || out.index( CONFIRMATION + " " )
end
if @log.debug?
@log.debug "#{cmd.inspect} finished"
@log.debug " stdout --> #{out.inspect}"
@log.debug " stderr --> #{err.inspect}"
end
if @shell.open?
match = out.match( /#{CONFIRMATION} /o )
out = match.pre_match
status = match.post_match.strip.to_i
else
status = 0
end
CommandOutput.new( out, ( err.empty? ? nil : err ), status )
end
# Sends the given string directly to the shell, without waiting for
# any response.
def send_data( data )
@shell.send_data data
end
# Dends the given string directly to the shell as the given type of
# data, without waiting for any response.
def send_extended_data( type, data )
@shell.send_extended_data type, data
end
# Reinterprets missing methods as requests to execute commands. The
# parameters to the method are concatenated together with spaces
# and sent to the shell via #send_command.
def method_missing( sym, *args )
cmd = sym.to_s
cmd << " " << args.join( " " ) unless args.empty?
send_command cmd
end
end
end # SyncShell
end # Service
end # SSH
end # Net
|