Recipe 15.13 Controlling Another Program with Expect

15.13.1 Problem

You want to automate interaction with a full-screen program that expects to have a terminal behind STDIN and STDOUT.

15.13.2 Solution

Use the Expect module from CPAN:

use Expect;

$command = Expect->spawn("program to run")
    or die "Couldn't start program: $!\n";

# prevent the program's output from being shown on our STDOUT
$command->log_stdout(0);

# wait 10 seconds for "Password:" to appear
unless ($command->expect(10, "Password")) {
    # timed out
}

# wait 20 seconds for something that matches /[lL]ogin: ?/
unless ($command->expect(20, -re => '[lL]ogin: ?')) {
    # timed out
}

# wait forever for "invalid" to appear
unless ($command->expect(undef, "invalid")) {
    # error occurred; the program probably went away
}

# send "Hello, world" and a carriage return to the program
print $command "Hello, world\r";

# if the program will terminate by itself, finish up with
$command->soft_close( );

# if the program must be explicitly killed, finish up with
$command->hard_close( );

15.13.3 Discussion

This module requires two other modules from CPAN: IO::Pty and IO::Stty. It sets up a pseudo-terminal to interact with programs that insist on talking to the terminal device driver. People often use this for talking to passwd to change passwords. telnet (Net::Telnet, described in Recipe 18.6, is probably more suitable and portable) and ftp are also programs that expect a real tty.

Start the program you want to run with Expect->spawn, passing a program name and arguments either in a single string or as a list. Expect starts the program and returns an object representing that program, or undef if the program couldn't be started.

To wait for the program to emit a particular string, use the expect method. Its first argument is the number of seconds to wait for the string, or undef to wait forever. To wait for a string, give that string as the second argument to expect. To wait for a regular expression, give "-re" as the second argument and a string containing the pattern as the third argument. You can give further strings or patterns to wait for:

$which = $command->expect(30, "invalid", "success", "error", "boom");
if ($which) {
    # found one of those strings
}

In scalar context, expect returns the number of arguments it matched. In the previous example, expect would return 1 if the program emitted "invalid", 2 if it emitted "success", and so on. If none of the patterns or strings matches, expect returns false.

In list context, expect returns a five-element list. The first element is the number of the pattern or string that matched, which is the same as its return value in scalar context. The second argument is a string indicating why expect returned. If there were no error, the second argument would be undef. Possible errors are "1:TIMEOUT", "2:EOF", "3:spawn id(...)died", and "4:...". (See the Expect(3) manpage for the precise meaning of these messages.) The third argument of expect's return list is the string matched. The fourth argument is text before the match, and the fifth argument is text after the match.

Sending input to the program you're controlling with Expect is as simple as using print. The only catch is that terminals, devices, and sockets all vary in what they send and expect as the line terminatorwe've left the sanctuary of the C standard I/O library, so the behind-the-scenes conversion to "\n" isn't taking place. We recommend trying "\r" at first. If that doesn't work, try "\n" and "\r\n".

When you're finished with the spawned program, you have three options. One, you can continue with your main program, and the spawned program will be forcibly killed when the main program ends. This will accumulate processes, though. Two, if you know the spawned program will terminate normally either when it has finished sending you output or because you told it to stopfor example, telnet after you exit the remote shellcall the soft_close method. If the spawned program could continue forever, like tail -f, then use the hard_close method; this kills the spawned program.

15.13.4 See Also

The documentation for the Expect, IO::Pty, and IO::Stty modules from CPAN; Exploring Expect, by Don Libes (O'Reilly)