Ruby for system administration

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

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

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 One-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

____________________________________________________________________
http://www.freemail.gr - äùñåÜí õðçñåóßá çëåêôñïíéêïý ôá÷õäñïìåßïõ.
http://www.freemail.gr - free email service for the Greek-speaking.

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.

"Farrel Lifson" <farrel.lifson@gmail.com> 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

···

--
A morning without coffee is like something without something else.