Chaining external commands using pipes

I have come up with a little script that works well in IRB for issuing shell commands and piping them together. For example

  require './shell.rb'

  io = Sh.ls("/bin").grep("grep")
  puts io

generates

  bzegrep
  bzfgrep
  bzgrep
  egrep
  fgrep
  grep
  zegrep
  zfgrep
  zgrep

I am curious as to whether the way I have implemented it is the best way. It seems kind of clunky to me. I have to spawn a new Ruby process using fork and then reopen $stdin on the child process and connect it
to the output stream of the previous command. I was hoping to find
a way to get around forking ruby processes to do this. Any
ideas

The source code is below or syntax highlighted here
http://xtargets.com/snippets/posts/show/77

Brad

···

--------------------------------

module Sh
     class Command

         def initialize(cmd, *args)
             @cmd = cmd
             @args = args
             @io = nil
         end

         def inspect
             to_s
         end

         def to_str
             to_s
         end

         def to_s
             call
             str = @io.read
             @io.close
             str
         end

         def | other
             call
             other.call(@io)
         end

         def method_missing(name, *args)
             self | Command.new(name, *args)
         end

         def call (io=nil)

             # The @io object can only be assigned
             # once
             return self if @io

             command = @cmd.to_s + " " + @args.join(" ")

             if io
                 # We need a new process
                 # and a pipe to connect the
                 # this command to the previous
                 # command in the chain.
                 rd, wr = IO.pipe

                 if fork
                     # Parent
                     wr.close
                 else
                     # Child
                     rd.close
                     begin
                         # Reconnect the STDIN of the
                         # new process to the io parameter
                         $stdin.reopen(io)
                         IO.popen command do |f|
                             while txt = f.read(4096)
                                 wr.write txt
                             end
                         end
                     rescue Errno::EPIPE
                         # Ignore the broken pipe
                         # cause the other process
                         # has thrown away the pipe
                         # earlier than we used outs
                     ensure
                         wr.close
                     end
                     exit
                 end
                 @io = rd
             else
                 # This command is executed standalone.
                 @io = IO.popen command
             end
             self
         end
     end

     def self.method_missing (name, *args)
         Command.new(name, *args)
     end
end

Stdlib shell (require 'shell') does much of that IIRC.
( http://ruby-doc.org/stdlib/libdoc/shell/rdoc/classes/Shell.html )

···

On Thursday 06 September 2007 11:35:05 pm Brad Phelan wrote:

I have come up with a little script that works well in IRB for issuing
shell commands and piping them together. For example

  require './shell.rb'

  io = Sh.ls("/bin").grep("grep")
  puts io

generates

  bzegrep
  bzfgrep
  bzgrep
  egrep
  fgrep
  grep
  zegrep
  zfgrep
  zgrep

I am curious as to whether the way I have implemented it is the best
way. It seems kind of clunky to me. I have to spawn a new Ruby process
using fork and then reopen $stdin on the child process and connect it
to the output stream of the previous command. I was hoping to find
a way to get around forking ruby processes to do this. Any
ideas

The source code is below or syntax highlighted here
http://xtargets.com/snippets/posts/show/77

Brad

--------------------------------

module Sh
     class Command

         def initialize(cmd, *args)
             @cmd = cmd
             @args = args
             @io = nil
         end

         def inspect
             to_s
         end

         def to_str
             to_s
         end

         def to_s
             call
             str = @io.read
             @io.close
             str
         end

         def | other
             call
             other.call(@io)
         end

         def method_missing(name, *args)
             self | Command.new(name, *args)
         end

         def call (io=nil)

             # The @io object can only be assigned
             # once
             return self if @io

             command = @cmd.to_s + " " + @args.join(" ")

             if io
                 # We need a new process
                 # and a pipe to connect the
                 # this command to the previous
                 # command in the chain.
                 rd, wr = IO.pipe

                 if fork
                     # Parent
                     wr.close
                 else
                     # Child
                     rd.close
                     begin
                         # Reconnect the STDIN of the
                         # new process to the io parameter
                         $stdin.reopen(io)
                         IO.popen command do |f|
                             while txt = f.read(4096)
                                 wr.write txt
                             end
                         end
                     rescue Errno::EPIPE
                         # Ignore the broken pipe
                         # cause the other process
                         # has thrown away the pipe
                         # earlier than we used outs
                     ensure
                         wr.close
                     end
                     exit
                 end
                 @io = rd
             else
                 # This command is executed standalone.
                 @io = IO.popen command
             end
             self
         end
     end

     def self.method_missing (name, *args)
         Command.new(name, *args)
     end
end

--
Konrad Meyer <konrad@tylerc.org> http://konrad.sobertillnoon.com/

Stdlib shell (require 'shell') does much of that IIRC.
( http://ruby-doc.org/stdlib/libdoc/shell/rdoc/classes/Shell.html )

Perhaps but there is zero documentation for any of the classes in the shell library and I don't see anything specific to creating pipes
between processes there.

Brad