Ruby for system administration


#1

Hi,

since I don’t like bash or other shell languages too much, I’ve started
writing more and more system administration scripts in ruby, which works
great and saves much time. I invoke the commands which should be
executed with ‘Kernel.`’.

But there’s one problem with that. If my_command produces output when
being executed directly from the shell prompt, I won’t see it when I
call it in a ruby script with my_command.

To get along this I normally use

$stdout << `my_command`

but then the output will not be shown until my_command finishes. If
my_command takes some time, the user running the script will have no
feedback.

Is there a way to directly redirect my_command’s output to stdout? Or
are there other ways to work arround this?

Much thanks in advance,
Tassilo


#2

You might want to look at IO.popen. It allows you to run a command and
communicate with it (and receive output) via pipes which to your
script will just be an IO object with all the associated methods like
readlines,puts etc etc. I’m using it in a project currently and it’s
quite easy to use.

Farrel


#3

“Farrel L.” removed_email_address@domain.invalid writes:

Hi Farrel,

You might want to look at IO.popen. […]

I’ll have a look at it later this day. But the things you mentioned
sound quite good, I think.

Thanks,
Tassilo


#4

Tassilo Horn wrote:

Is there a way to directly redirect my_command’s output to stdout? Or
are there other ways to work arround this?
I use IO.popen and Observer to get progress reports for commands.
I have been building an automation framework together with what I call a
test integration framework for my current project.
At th emoment the project is in full steam and there is absolutely no
chance to release the code for public consumption (meaning there is no
time to remove project specific stuff and wrap the whole thing as a
public library that is half-way stable).
Just so, here’s what I have done in the lowest level. A Command module
to define a stable interface and DOSCommand which I use to execute
commands on a windows shell (which in turn uses the class that led to
this whole Command thing and which actually is my answer to your
question :slight_smile: ).
I have several commands, my favorite being the PressAnyKeyCommand :slight_smile:

#This module is the interface for execution of arbitrary commands where
we
#need access to the success status, output and execution time for a
command
#
#Check DOSCommand and RubyCommand for some implementations
module Command
attr_writer :output
def name
return @name
end
def output
init
return @output
end
def working_directory
init
return @working_directory
end
def exec_time
init
return @exec_time
end
def success?
init
return @success
end
def run?
init
return @run
end

   def run
     init
     @run=true
   end

   def status verbose=false
     init
     msg=""
     if run?
       st="succeeded"
       st="failed" unless @success
       msg+="#{@name} #{st} in #{@exec_time}s\n"
     else
       msg+="#{@name} was not executed\n"
     end
     msg+="Working directory:

#{@working_directory}\nLog:\n#{@output}\n" if (!success?||verbose)
return msg
end
#This method initializes the module members with sane values.
#
#It also checks that working_directory is a directory (it
raises RuntimeError if it isn’t)
#
#It’s a good idea to use this method in the initialize method of
any objects including this module
#but it isn’t necessary.
def init name="",working_directory=nil
begin
@name=name
@output="" unless @output
@exec_time=0 unless @exec_time
@working_directory=working_directory
@working_directory=Dir.pwd unless @working_directory
raise “Non-existent working directory: #{working_directory}”
unless File.directory?(@working_directory)
@run=false unless @run
@success=false unless @success
end unless @name
end
end
#Class that wraps the execution of a command in a command shell.
class DOSCommand
include Command
attr_reader:command_line
#If working_directory is specified then the command will be
executed in that directory.
def initialize name,command_line,working_directory=nil
init(name,working_directory)
@command_line=command_line
@cmd=RivaLib::Win::ExecCmd.new(@command_line)
end

   #Runs the command.
   def run
     @run=true
     prev_dir=Dir.pwd
     begin
       Dir.chdir(@working_directory) if @working_directory
       @cmd.run
       @success=@cmd.success?
       @output=@cmd.output
       @exec_time=@cmd.exec_time
       return @success
     rescue SystemCallError
       return false
     ensure
       Dir.chdir(prev_dir)
     end
   end

#Executes a command on a dos shell, redirecting stderr to stdout
(“2>&1”)
#
#You can then access the output and the return value for the
command.
#
#This is meant as a last-resort replacement for popen3 (because of
problems with VC++6.0 and the Ruby O.-Click Installer).
#
#exec_time provides the Time spent running the command.
class ExecCmd
attr_reader :output,:cmd,:exec_time
#When a block is given, the command runs before yielding
def initialize cmd
@output=""
@exec_time=0
@cmd=cmd
@cmd_run=cmd+" 2>&1" unless cmd=~/2>&1/
if block_given?
run
yield self
end
end

   #Runs the command
   def run
     t1=Time.now
     IO.popen(@cmd_run){|f|
       @output=f.read
       @process=Process.waitpid2(f.pid)[1]
     }
     @exec_time=Time.now-t1
   end
   #Returns false if the command hasn't been executed yet
   def run?
     return false unless @process
     return true
   end
   #Returns the exit code for the command.
   #
   #Returns nil if the command hasn't run yet.
   def exitcode
     return @process.exitstatus if @process
     return nil
   end
   #Returns true if the command was succesfull.
   #
   #Will return false if the command hasn't been executed
   def success?
     return @process.success? if @process
     return false
   end
 end


http://www.braveworld.net/riva


#5

Tassilo Horn wrote:

Is there a way to directly redirect my_command’s output to stdout? Or
are there other ways to work arround this?

system ‘my_command’ does this.