From: Brian Candler Date: 2007-09-24T19:56:00+09:00 Subject: Re: Net::SSH expect like interface --G4iJoqBmSsgzjUCe Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Thu, Apr 26, 2007 at 05:56:34PM +0900, Manish Sapariya wrote: > I have been looking for expect like interface for Net::SSH lib, > but apparently there is none. This is something I've been wanting for a long time too, ideally to use ssh as a drop-in replacement for Net::Telnet. I've made a first step by monkey-patching Net::SSH so that the process.popen3 interface is also available for shell sessions, just by passing in nil as the command name (see attached). Once you've done this, you can drive Net::SSH like this: ------------------------------------------------------------------------ require 'rubygems' require 'net/ssh' class ShellSession def initialize(*args) @session = Net::SSH.start(*args) @inp, @out, @err = @session.process.popen3 # nil means shell end def cmd(command, prompt = /[>\#\$?] ?\z/) puts "(#{command})" @inp.puts command res = "" while true @out.channel.connection.process # block for incoming data res << @out.read if @out.data_available? res << @err.read if @err.data_available? break if res =~ prompt end res end end s = ShellSession.new("x.x.x.x", "cisco", "cisco") puts s.cmd("term len 0") puts s.cmd("show ver") puts s.cmd("show run") puts s.cmd("enable", /assword:/) puts s.cmd("cisco") puts s.cmd("show run") ------------------------------------------------------------------------ However this is only part of the story. Both IO#expect and Net::Telnet expect a real IO object that you can use as an argument to Kernel#select. One solution may be to make a Socketpair: s1, s2 = Socket.pair(Socket::AF_LOCAL, Socket::SOCK_STREAM, 0) However, then the code which copies the other end of the pair to/from the ssh session needs to run as a thread. It's probably also not portable to Windows. Alternatively, a modified version of Net::Telnet can be made. And in that case, it could use the ssh channel interface directly and so not rely on a patched Net::SSH. Regards, Brian. --G4iJoqBmSsgzjUCe Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="ssh-process-shell.diff" --- lib/net/ssh/service/process/open.rb.orig 2007-09-24 11:14:25.000000000 +0100 +++ lib/net/ssh/service/process/open.rb 2007-09-24 11:16:12.000000000 +0100 @@ -38,7 +38,7 @@ # attempt to execute the given command. If a block is given, the # manager will be yielded to the block, and the constructor will not # return until all channels are closed. - def initialize( connection, log, command ) + def initialize( connection, log, command=nil ) @log = log @command = command @channel = connection.open_channel( @@ -129,7 +129,11 @@ def do_confirm( channel ) channel.on_success(&method(:do_exec_success)) channel.on_failure(&method(:do_exec_failure)) - channel.exec @command, true + if @command + channel.exec @command, true + else + channel.send_request "shell", nil, true + end end # Invoked when the invocation of the command has been successful. @@ -151,7 +155,7 @@ @on_failure.call( self, nil ) else raise Net::SSH::Exception, - "could not execute process (#{@command})" + @command ? "could not execute process (#{@command})" : "could not start shell" end end --- lib/net/ssh/service/process/popen3.rb.orig 2007-09-24 11:14:29.000000000 +0100 +++ lib/net/ssh/service/process/popen3.rb 2007-09-24 11:15:02.000000000 +0100 @@ -40,7 +40,7 @@ # is not given, the input, output, and error channels are returned # and the process *might* not terminate until the session itself # terminates. - def popen3( command ) + def popen3( command=nil ) @connection.open_channel( "session" ) do |chan| chan.on_success do |ch| @@ -60,7 +60,11 @@ chan.close end - chan.exec command, true + if command + chan.exec command, true + else + chan.send_request "shell", nil, true + end end @connection.loop --- lib/net/ssh/service/process/driver.rb.orig 2007-09-24 11:21:35.000000000 +0100 +++ lib/net/ssh/service/process/driver.rb 2007-09-24 11:22:04.000000000 +0100 @@ -126,7 +126,7 @@ @handlers = handlers end - def open( command ) + def open( command=nil ) @log.debug "opening '#{command}'" if @log.debug? process = @handlers[ :open ].call( command ) @@ -139,7 +139,7 @@ process end - def popen3( command, &block ) + def popen3( command=nil, &block ) @log.debug "popen3 '#{command}'" if @log.debug? mgr = @handlers[ :popen3 ] mgr.popen3( command, &block ) --G4iJoqBmSsgzjUCe--