From: Mike Wilson Date: 2001-02-28T02:41:24+09:00 Subject: [ruby-talk:11703] looping, need to execute something only once My brain is fried, and I can't think of how to do this (sorry if I don't explain this well) I'm creating a bunch of telnet connections for a single user, and logging that user into each connection. Then I have a loop that prompts for a command and sends that command to each of the connections (i.e. you can issue a single command on multiple machines). I usually want to loop for each host, but sometimes I want something to execute only once. I'd like not to have to test within the loop for what the command is, I'd rather be able to just pass it along. I'm sure this is easy, but I can't think of a good way right now. # create connections user = User.new('abheww6', 'secret') ['orion', 'oscar'].each do |host| Connection.new({'Host'=>host}, user) end # Connection.currentConnections is an accessor to a class variable # @@currentConnections = {} # every time the Connection.initialize is called we push self onto it # login to each Connection.currentConnections[user].each do |con| con.loginOnConnection end begin while true print "Enter command: " cmd = $stdin.readline.chomp #Connection.currentConnections[user].each do |con| foo = Connection.currentConnections[user].dup foo.each do |con| con.executeCmd(cmd) {|c| puts c} # If 'cmd' looks like !LOGIN(somemachine), I want # to create that connection to act on in the future # but because I'm in a loop, it logs into that machine # once for every host that I'm currently acting on. If I # don't dup the @@currentConnections (above: foo =) # it adds the object, has that one to loop on, so it # does it again ad infinitum. I can't think of how I # can add the connection once and be done with it. #con.executeCmd("!LOGIN(rana)") {|c| puts c} end end rescue Interrupt, EOFError puts Connection.currentConnections[user].each do |con| con.logoffConnection end end -- WARNING: full code below for those who need more context (this isn't finished, be gentle ;) require 'net/telnet' module Net class Telnet def to_s @options['Host'] end end end module SuperTelnet class User @@currentUsers = [] attr_accessor :username, :passwd, :suuser, :supasswd def initialize(user, pass) @username = user @passwd = pass @@currentUsers << self end def User.currentUsers @@currentUsers end end class Connection @@currentConnections = {} def initialize(aHash, userObj) @conOptions = aHash @thisUser = userObj raise ArgumentError if not @thisUser.kind_of? SuperTelnet::User if not @@currentConnections.has_key? @thisUser @@currentConnections[@thisUser] = [] end if not @conOptions.has_key? 'Prompt' @conOptions['Prompt'] = Regexp.new('[#$%>] \z', false, 'n') end if not @conOptions.has_key? 'Timeout' @conOptions['Timeout'] = 10 end if not @conOptions.has_key? 'Port' @conOptions['Port'] = 23 end @thisConnection = Net::Telnet.new(@conOptions) @@currentConnections[@thisUser] << self end def Connection.currentConnections @@currentConnections end #def method_missing(methodId) # executeCmd(self.instance_eval(methodId.id2name)) do |c| # yield c if block_given? # end #end def loginOnConnection @thisConnection.login(@thisUser.username, @thisUser.passwd) end def logoffConnection @@currentConnections[@thisUser].delete(self) @thisConnection.close end def switchUser(suuser, supasswd) tmpHash = @conOptions tmpHash['String'] = "su - #{suuser}" tmpHash['Match'] = /[pP]assword:/ @thisConnection.cmd(tmpHash) do |c| yield c if block_given? end tmpHash['String'] = supasswd tmpHash['Match'] = @conOptions['Prompt'] @thisConnection.cmd(tmpHash) do |c| yield c if block_given? end end def executeCmd(cmd) output = case cmd when /^!(.*)/ executeAsInternalCmd($1) when /^\?(.*)/ runCmdConditionally($1) else runCmdOnConnection(cmd) end if output and not output.empty? and block_given? yield "\n#{@thisConnection}:\n#{output}\n\n" end end private def runCmdOnConnection(cmd) output = [] @thisConnection.cmd({ 'String' => cmd, 'Match' => /.+ \z/n, 'Timeout' => false }) do |c| if c.chomp != cmd output << c end end output end def runCmdConditionally(cmd) output = [] case cmd when /on\s*\((.*)\)\s+(.*)/ args = $1.split args.each do |arg| arg == @thisConnection.to_s and output << runCmdOnConnection($2) end else raise ArgumentError, "No such command: #{cmd}" end output end def executeAsInternalCmd(cmd) allowedCommands = ['SU', 'LOGIN', 'LOGOFF', 'HELP'] seen = false checker, *args = cmd.gsub(/(\w+)\s*\((.*)\)/, '\1 \2').split raise FormatError, "Bad command format: #{cmd}" if not checker allowedCommands.each do |thisCmd| seen = true if thisCmd =~ checker end raise ArgumentError, "No such command: #{cmd}" if not seen case checker when 'SU' user, pass = args switchUser(user, pass) when 'LOGIN' args.each do |arg| Connection.new({'Host' => arg}, @thisUser). loginOnConnection end nil when 'LOGOFF' args.each do |arg| logoffConnection if @thisConnection.to_s == arg end nil when 'HELP' puts "this is help" else raise ArgumentError, "No such command: #{cmd}" end end end end if __FILE__ == $0 include SuperTelnet user = User.new('abheww6', 'secret') ['orion', 'oscar'].each do |host| Connection.new({'Host'=>host}, user) end Connection.currentConnections[user].each do |con| con.loginOnConnection end #def foo # "ls -la /" #end begin while true print "Enter command: " cmd = $stdin.readline.chomp #Connection.currentConnections[user].each do |con| foo = Connection.currentConnections[user].dup foo.each do |con| con.executeCmd(cmd) {|c| puts c} #con.foo {|c| puts c} #con.executeCmd("ls") {|c| puts c} #con.executeCmd("?on(orion) ls -la") {|c| puts c} #con.executeCmd("!SU(root secret)") {|c| puts c} #con.executeCmd("!LOGIN(rana)") {|c| puts c} #con.executeCmd("ls") {|c| puts c} #con.executeCmd("whoami") {|c| puts c} end end rescue Interrupt, EOFError puts Connection.currentConnections[user].each do |con| con.logoffConnection end end end _________________________________________________________________ Get your FREE download of MSN Explorer at http://explorer.msn.com