There are several ways to spawn a separate process; the easiest is to
run some command and wait for it to complete. You might find yourself
doing this to run some separate command or retrieve data from the host
system. Ruby does this for you with the
system
and backquote
methods.
system("tar xzf test.tgz")
|
� |
tar: test.tgz: Cannot open: No such file or directory\ntar: Error is not recoverable: exiting now\ntar: Child returned status 2\ntar: Error exit delayed from previous errors\nfalse
|
result = `date`
|
result
|
� |
"Sun Jun 9 00:08:50 CDT 2002\n"
|
The method
Kernel::system
executes the given command in a
subprocess; it returns
true
if the command was
found and executed properly,
false
otherwise. In case of
failure, you'll find the subprocess's exit code in the global variable
$?
.
One problem with
system
is that the command's output will
simply go to the same destination as your program's output, which may
not be what you want. To capture the standard output of a
subprocess, you can use the backquotes, as with
`date`
in
the previous example. Remember that you may need to use
String#chomp
to remove the line-ending characters from the
result.
Okay, this is fine for simple cases---we can run some other process
and get the return status. But many times we need a bit more control
than that. We'd like to carry on a conversation with the subprocess,
possibly sending it data and possibly getting some back.
The method
IO.popen
does just this. The
popen
method
runs a command as a subprocess and connects that subprocess's
standard input and standard output to a Ruby
IO
object. Write to
the
IO
object, and the subprocess can read it on standard
input. Whatever the subprocess writes is available in the Ruby program
by reading from the
IO
object.
For example, on our systems one of the more useful utilities is
pig
, a program that reads words from standard input and prints
them in pig Latin (or igpay atinlay). We can use this when our Ruby
programs need to send us output that our 5-year-olds shouldn't be able to
understand.
pig = IO.popen("pig", "w+")
pig.puts "ice cream after they go to bed"
pig.close_write
puts pig.gets
|
produces:
iceway eamcray afterway eythay ogay otay edbay
|
This example illustrates both the apparent simplicity and the real-world
complexities involved in driving subprocesses through pipes. The code
certainly looks simple enough: open the pipe, write a phrase, and read
back the response. But it turns out that the
pig
program doesn't
flush the output it writes. Our original attempt at this example,
which had a
pig.puts
followed by a
pig.gets
, hung forever.
The
pig
program processed our input, but its response was never
written to the pipe. We had to insert the
pig.close_write
line.
This sends an end-of-file to
pig
's standard input, and the output
we're looking for gets flushed as
pig
terminates.
There's one more twist to
popen
. If the command you pass it
is a single minus sign (``--''),
popen
will fork a new Ruby
interpreter.
Both this and the original interpreter will continue
running by returning from the
popen
. The original process
will receive an
IO
object back, while the child will receive
nil
.
pipe = IO.popen("-","w+")
if pipe
pipe.puts "Get a job!"
$stderr.puts "Child says '#{pipe.gets.chomp}'"
else
$stderr.puts "Dad says '#{gets.chomp}'"
puts "OK"
end
|
produces:
Dad says 'Get a job!'
Child says 'OK'
|
In addition to
popen
, the traditional Unix calls
Kernel::fork
,
Kernel::exec
, and
IO.pipe
are
available on platforms that support them. The file-naming convention
of many
IO
methods and
Kernel::open
will also spawn
subprocesses if you put a ``
|
''
as the first character of the
filename (see the introduction to class
IO
on page 325 for
details). Note that you
cannot create pipes using
File.new
; it's just for files.