Recipe 23.1. Scripting an External Program
Problem
You want to automatically control an external program that expects to get terminal input from a human user.
Solution
When you're running a program that only needs a single string of input, you can use
IO.popen, as described in Recipe 20.8. This method runs a command, sends it a string as standard input, and returns the contents of its standard output:
def run(command, input='')
IO.popen(command, 'r+') do |io|
io.puts input
io.close_write
return io.read
end
end
run 'wc -w', 'How many words are in this string?' # => "7\n"
This technique is commonly used to invoke a command with sudo, which expects the user's password on standard input. This code obtains a user's password and runs a command on his behalf using sudo:
print 'Enter your password for sudo: '
sudo_password = gets.chomp
run('sudo apachectl graceful', user_password)
Discussion
IO.popen is a good way to run noninteractive commandscommands that read all their standard input at once and produce some output. But some programs are interactive; they send prompts to standard output, and expect a human on the other end to respond with more input.
On Unix, you can use Ruby's standard PTY and expect libraries to spawn a command and impersonate a human on the other end. This code scripts the Unix passwd command:
require 'expect'
require 'pty'
print 'Old password:'
old_pwd = gets.chomp
print "\nNew password:"
new_pwd = gets.chomp
PTY.spawn('passwd') do |read,write,pid|
write.sync = true
$expect_verbose = false
# If 30 seconds pass and the expected text is not found, the
# response object will be nil.
read.expect("(current) UNIX password:", 30) do |response|
write.print old_pwd + "\n" if response
end
# You can use regular expressions instead of strings. The code block
# will give you the regex matches.
read.expect(/UNIX password: /, 2) do |response, *matches|
write.print new_pwd + "\n" if response
end
# The default value for the timeout is 9999999 seconds
read.expect("Retype new UNIX password:") do |response|
write.puts new_pwd + "\n" if response
end
end
The read and write objects in the PTY#spawn block are IO objects. The expect library defines the IO#expect method found throughout this example.
See Also
Recipe 20.8, "Driving an
External Process with popen" Recipe 21.9, "Reading a Password," shows how to obtain a password without echoing it to the screen
 |