2

I am trying to run a shell script and capture the PID, STDERR, STDOUT and the exit status of the shell.

I am using Ruby 1.8.7, so Open3 doesn't have a way for getting the PID. I tried using the open4 gem, but unfortunately a few scripts hung while in the write process, which runs fine when manually run.

I would like to find an alternative. Your guidance will be much appreciated!

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Jay
  • 476
  • 1
  • 5
  • 21
  • You get the current PID in Ruby with Process.pid. Let me know if this helps – Raghu Aug 30 '13 at 21:38
  • Thanks Raghu, I am looking for PID for each shell process spawned and not the PID of current process, Sorry If my writing is misleading you.. – Jay Aug 30 '13 at 22:02
  • Can you post some example code, so we can try to reproduce the issue? – iblue Aug 30 '13 at 22:06
  • Thanks for your reply @iblue, I am not suppose to share the shell which has few inhouse info, sorry about that.. what I require is an alternate for achieving this through ruby. Below is the strace where it gets hung "process 32187 attached - interrupt to quit wait4(-1,  ". – Jay Aug 30 '13 at 22:10
  • 1.8.7 is going to limit you. Open3::popen3 in Ruby v2.0 has all the tricks you want. – the Tin Man Aug 31 '13 at 02:23
  • Yes @theTinMan I agree, thanks for editing... – Jay Aug 31 '13 at 03:58
  • 1
    If you cannot share actual code, write an example which has the same issue. With popen4, you may need to worry about how you are processing captured IO, processes with a lot of output can block if you do not keep up with it. – Neil Slater Aug 31 '13 at 08:15

1 Answers1

0

Unfortunately there isn't an easy way to do this. I tried PTY.spawn but sometimes it would fail to exec. If you can't use open3, then you can use FIFOs, but it gets a bit messy. Here's a solution I used on 1.8.7:

# Avoid each thread having copies of all the other FDs on the fork
def closeOtherFDs
  ObjectSpace.each_object(IO) do |io|
    unless [STDIN, STDOUT, STDERR].include?(io)
      unless(io.closed?)
        begin
          io.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
        rescue ::Exception => err
        end
      end
    end
  end
end


# Utilities for fifo method
require 'tempfile'
def tmpfifo
  # Use tempfile just to manage naming and cleanup of the file
  fifo = Tempfile.new('fifo.').path
  File.delete(fifo)
  system("/usr/bin/mkfifo",fifo)
  #puts "GOT: #{fifo} -> #{$?}"
  fifo
end

# fifo homebrew method
def spawnCommand *command
  ipath = tmpfifo
  opath = tmpfifo
  #epath = tmpfifo

  pid = fork do
    #$stdin.reopen(IO.open(IO::sysopen(ipath,Fcntl::O_RDONLY)))
    $stdin.reopen(File.open(ipath,'r'))
    $stdout.reopen(File.open(opath,'w'))
    #$stderr.reopen(File.open(epath,'w'))
    $stderr.close
    closeOtherFDs
    exec(*command)
    exit
  end

  i = open ipath, 'w'
  #i.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) # Doesn't work?  Use closeOtherFDs
  o = open opath, 'r'
  #e = open epath, 'r'
  e = nil
  [o,i,e].each { |p| p.sync = true if p }

  [o,i,e]
end

Nope, it's not clean. But because it uses fork and deals with the three handles itself, then you can get the PID and accomplish what open3 does.

Make sure to close your filehandles after! A yield version of this that cleans up afterwards would probably make more sense.

  • As a note, the reason I have the 'closeOtherFDs' routine to find and close all stdin/stdout/stderr objects in the children is because I was spawning off 100s of processes and I was running out of filehandles. If you are only spawning a few, then you can probably take that code out. – David Ljung Madison Stellar Oct 16 '13 at 10:36
  • And also, I just realized the code I pasted in was closing stderr, but you should be able to figure out how to use a tmpfifo for that as well. – David Ljung Madison Stellar Oct 16 '13 at 10:37
  • Thanks @David Ljung Madison for your time in helping me, I just want to keep it simple as I have thousands of process to be spawned. Anyway I have used the backtick way of execution to move the output based on the exit status. Thanks again!! – Jay Oct 29 '13 at 16:23
  • No problem. No clue why I was downvoted, a little weird that they weren't able to share what problem they had with my solution which, as far as I can tell, answers your original question quite well, even if you decided to take a different route. (And feel free to vote my answer up! :) – David Ljung Madison Stellar Oct 31 '13 at 08:36