16. Process Management and Communication

Gathering Output from a Program

output = `program args`       # collect output into one multiline string
output = `program args`.to_a  # collect output into array, one line per element

output = ""
IO.popen("ls") do |readme|
  readme.each do |line|
    output << line
  end
end

`fsck -y /dev/rsd1a`  # BAD AND SCARY in Perl because it's managed by the shell
                      # I donna in Ruby ...

# so the "clean and secure" version
readme, writeme = IO.pipe
pid = fork {
    # child
    $stdout.reopen writeme
    readme.close
    exec('find', '..')
}
# parent
writeme.close
readme.each do |line|
    # do something with 'line'
end
Process.waitpid(pid)

Running Another Program

status = system("xemacs #{myfile}")

status = system("xemacs", myfile)

system("cmd1 args | cmd2 | cmd3 >outfile")
system("cmd args <infile >outfile 2>errfile")

# stop if the command fails
abort "$program exited funny: #{$?}" unless system("cmd", "args1", "args2")

# get the value of the signal sent to the child
# even if it is a SIGINT or SIGQUIT
arglist = ['ruby', '-e', '5.times {|i| p i}']
system(*arglist)
raise "program killed by signal #{$?}" if ($? & 127) != 0

pid = fork {
    trap("SIGINT", "IGNORE")
    exec("sleep", "10")
}
trap ("SIGINT") {
    puts "Tsk tsk, no process interruptus"
}
Process.waitpid(pid, 0)

# Ruby permits to lie to the program called by a 'system'.
# (ie specify what return argv[0] in C, $0 in Perl/Ruby ...)
system ['bash', 'fake'], '-c', 'echo $0'

Replacing the Current Program with a Different One

exec("archive *.data")

exec("archive", "accounting.data")

exec("archive accounting.data")

Reading or Writing to Another Program

# read the output of a program
IO.popen("ls") {|readme|
    while readme.gets do
        # ...
    end
}
# or
readme = IO.popen("ls")
while readme.gets do
    # ...
end
readme.close

# "write" in a program
IO.popen("cmd args","w") {|pipe|
    pipe.puts("data")
    pipe.puts("foo")
}

# close wait for the end of the process
read = IO.popen("sleep 10000") # child goes to sleep
read.close                     # and the parent goes to lala land

writeme = IO.popen("cmd args", "w")
writeme.puts "hello" # program will get hello\n on STDIN
writeme.close        # program will get EOF on STDIN

# send in a pager (eg less) all output
$stdout = IO.popen("/usr/bin/less","w")
print "huge string\n" * 10000

Filtering Your Own Output

#-----------------------------
def head(lines = 20)
    pid = open("|-","w")
    if pid == nil
        return
    else
        while gets() do
            pid.print
            lines -= 1
            break if lines == 0
        end
    end
    exit
end

head(100)
while gets() do
    print
end
#-----------------------------
1: > Welcome to Linux, version 2.0.33 on a i686

2: > 

3: >     "The software required `Windows 95 or better', 

4: >      so I installed Linux."  
#-----------------------------
> 1: Welcome to Linux, Kernel version 2.0.33 on a i686

> 2: 

> 3:     "The software required `Windows 95 or better', 

> 4:      so I installed Linux."  
#-----------------------------
#!/usr/bin/ruby
# qnumcat - demo additive output filters

def number()
    pid = open("|-","w")
    if pid == nil
        return
    else
        while gets() do pid.printf("%d: %s", $., $_); end
    end
    exit
end

def quote()
    pid = open("|-","w")
    if pid == nil
        return
    else
        while gets() do pid.print "> #{$_}" end
    end
    exit
end

number()
quote()

while gets() do
    print
end
$stdout.close
exit

Preprocessing Input

ARGV.map! { |arg|
    arg =~ /\.(gz|Z)$/ ? "|gzip -dc #{arg}" : arg
}
for file in ARGV
    fh = open(file)
    while fh.gets() do
        # .......
    end
end
#-----------------------------
ARGV.map! { |arg|
    arg =~ %r#^\w+://# ? "|GET #{arg}" : arg   #
}
for file in ARGV
    fh = open(file)
    while fh.gets() do
        # .......
    end
end
#-----------------------------
pwdinfo = (`domainname` =~ /^(\(none\))?$/) ? '/etc/passwd' : '|ypcat  passwd';
pwd = open(pwdinfo);
#-----------------------------
puts "File, please? ";
file = gets().chomp();
fh = open(file);

Reading STDERR from a Program

output = `cmd 2>&1`                            # with backticks
# or
ph = open("|cmd 2>&1")                         # with an open pipe
while ph.gets() { }                            # plus a read
#-----------------------------
output = `cmd 2>/dev/null`                     # with backticks
# or
ph = open("|cmd 2>/dev/null")                  # with an open pipe
while ph.gets() { }                            # plus a read
#-----------------------------
output = `cmd 2>&1 1>/dev/null`                # with backticks
# or
ph = open("|cmd 2>&1 1>/dev/null")             # with an open pipe
while ph.gets() { }                            # plus a read
#-----------------------------
output = `cmd 3>&1 1>&2 2>&3 3>&-`             # with backticks
# or
ph = open("|cmd 3>&1 1>&2 2>&3 3>&-")          # with an open pipe
while ph.gets() { }                            # plus a read
#-----------------------------
system("program args 1>/tmp/program.stdout 2>/tmp/program.stderr") 
#-----------------------------
output = `cmd 3>&1 1>&2 2>&3 3>&-`  
#-----------------------------
fd3 = fd1 
fd1 = fd2 
fd2 = fd3 
fd3 = undef 
#-----------------------------
system("prog args 1>tmpfile 2>&1") 
system("prog args 2>&1 1>tmpfile") 
#-----------------------------
# system ("prog args 1>tmpfile 2>&1") 
fd1 = "tmpfile"          # change stdout destination first
fd2 = fd1                # now point stderr there, too
#-----------------------------
# system("prog args 2>&1 1>tmpfile") 
fd2 = fd1                # stderr same destination as stdout
fd1 = "tmpfile"          # but change stdout destination 
#-----------------------------
# It is often better not to rely on the shell, 
# because of portability, possible security problems 
# and bigger resource usage. So, it is often better to use the open3 library. 
# See below for an example.
# opening stdin, stdout, stderr
require "open3"
stdin, stdout, stderr = Open3.popen('cmd')

Controlling Input and Output of Another Program

#-----------------------------
# Contrary to perl, we don't need to use a module in Ruby
fh = Kernel.open("|" + program, "w+")
fh.puts "here's your input\n"
output = fh.gets()
fh.close()
#-----------------------------
Kernel.open("|program"),"w+")    # RIGHT !
#-----------------------------
# Ruby has already object methods for I/O handles
#-----------------------------
begin
    fh = Kernel.open("|" + program_and_options, "w+")
rescue
    if ($@ ~= /^open/)
        $stderr.puts "open failed : #{$!} \n #{$@} \n"
        break
    end
    raise      # reraise unforseen exception
end

Controlling the Input, Output, and Error of Another Program

Communicating Between Related Processes

Making a Process Look Like a File with Named Pipes

Sharing Variables in Different Processes

Listing Available Signals

#% kill -l
#HUP INT QUIT ILL TRAP ABRT BUS FPE KILL USR1 SEGV USR2 PIPE
#ALRM TERM CHLD CONT STOP TSTP TTIN TTOU URG XCPU XFSZ VTALRM
#PROF WINCH POLL PWR
#-----------------------------
#% ruby -e 'puts Signal.list.keys.join(" ")'
#PWR USR1 BUS USR2 TERM SEGV KILL POLL STOP SYS TRAP IOT HUP INT                                                                          #
#WINCH XCPU TTIN CLD TSTP FPE IO TTOU PROF CHLD CONT PIPE ABRT
#VTALRM QUIT ILL XFSZ URG ALRM
#-----------------------------
# After that, the perl script create an hash equivalent to Signal.list, 
# and an array. The array can be obtained by :
signame = []
Signal.list.each { |name, i| signame[i] = name }

Sending a Signal

Process.kill(9, pid)                    # send $pid a signal 9
Process.kill(-1, Process.getpgrp())     # send whole job a signal 1
Process.kill("USR1", $$)                # send myself a SIGUSR1
Process.kill("HUP", pid1, pid2, pid3)   # send a SIGHUP to processes in @pids
#-----------------------------
begin
    Process.kill(0, minion)
    puts "#{minion} is alive!"
rescue Errno::EPERM                     # changed uid
    puts "#{minion} has escaped my control!";
rescue Errno::ESRCH
    puts "#{minion} is deceased.";      # or zombied
rescue
    puts "Odd; I couldn't check the status of #{minion} : #{$!}"
end

Installing a Signal Handler

Kernel.trap("QUIT", got_sig_quit)       # got_sig_quit = Proc.new { puts "Quit\n" }
trap("PIPE", "got_sig_quit")            # def got_sig_pipe ...
trap("INT") { ouch++ }                  # increment ouch for every SIGINT
#-----------------------------
trap("INT", "IGNORE")                   # ignore the signal INT
#-----------------------------
trap("STOP", "DEFAULT")                 # restore default STOP signal handling

Temporarily Overriding a Signal Handler

# the signal handler
def ding
    trap("INT", "ding")
    puts "\aEnter your name!"
end

# prompt for name, overriding SIGINT
def get_name
    save = trap("INT", "ding")

    puts "Kindly Stranger, please enter your name: "
    name = gets().chomp()
    trap("INT", save)
    name
end

Writing a Signal Handler

Catching Ctrl-C

Avoiding Zombie Processes

Blocking Signals

Timing Out an Operation

# implemented thanks to http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/1760
require 'timeout'

# we'll do something vastly more useful than cookbook to demonstrate timeouts
begin
    timeout(5) {
        waitsec = rand(10)
        puts "Let's see if a sleep of #{waitsec} seconds is longer than 5 seconds..."
        system("sleep #{waitsec}")
    }
    puts "Timeout didn't occur"
rescue Timeout::Error    
    puts "Timed out!"
end

Program: sigrand