Driving an External Process with popen

Problem

You want to execute an external command in a subprocess. You want to pass some data into its standard input stream, and read its standard output.

Solution

If you don care about the standard input side of things, you can just use the %x{} construction. This runs a string as a command in an operating system subshell, and returns the standard output of the command as a string.

%x{whoami} # => "leonardr " puts %x{ls -a empty_dir} # . # ..

If you want to pass data into the standard input of the subprocess, do it in a code block that you pass into the IO. popen method. Heres IO.popen used on a Unix system to invoke tail, a command that prints to standard output the last few lines of its standard input:

IO.popen( ail -3, +) do |pipe| 1.upto(100) { |i| pipe >> "This is line #{i}. " } pipe.close_write puts pipe.read end # This is line 98. # This is line 99. # This is line 100.

Discussion

IO.popens pawns a subprocess and creates a pipe: an IO stream connecting the Ruby interpreter to the subprocess. IO.popen makes the pipe available to a code block, just as File.open makes an open file available to a code block. Writing to the IO object sends data to the standard input of the subprocess; reading from it reads data from its standard output.

IO.popen takes a file mode, just like File.open. To use both the standard input and output of a subprocess, you need to open it in read-write mode ("r+").

A command that accepts standard input won really start running until its input stream is closed. If you use popen to run a command like tail, you must call pipe. close_write before you read from the pipe. If you try to read the subprocess standard output while the subprocess is waiting for you to send it data on standard input, both processes will hang forever.

The %{} construct and the popen technique work on both Windows and Unix, but scripts that use them won usually be portable, because its very unlikely that the command you e running exists on all platforms.

On Unix systems, you can also use popen to spawn a Ruby subprocess. This is like calling fork, except that the parent gets a read-write filehandle thats hooked up to the standard input and output of the child. Unlike with Kernel#fork (but like Cs implementation of fork), the same code block is called for the parent and the child. The presence or absence of the filehandle is the only way to know whether you e the parent or the child:

IO.popen(-, +) do |child_filehandle| if child_filehandle $stderr.puts "I am the parent: #{child_filehandle.inspect}" child_filehandle.puts 404 child_filehandle.close_write puts "My child says the square root of 404 is #{child_filehandle.read}" else $stderr.puts "I am the child: #{child_filehandle.inspect}" number = $stdin.readline.strip.to_i $stdout.puts Math.sqrt(number) end end # I am the child: nil # I am the parent: # # My child says the square root of 404 is 20.0997512422418

See Also

Категории