Chapter 21. Expect, C, And C++

In the other chapters of this book, I have described how to use Expect with the command-oriented Tcl environment. However, Expect can be used without Tcl. In this chapter, I will describe how to call Expect functions from C by using the Expect library. This library is compatible with C++ as well, and most of the examples are identical between C and C++. For other languages, you are on your own. However, if you know how to call C routines from your favorite language, you should be able to do the same with Expect.

Much of the functions in the library work analogously to their counterparts in the Expect program. Accordingly, it will be very helpful to have some experience with Expect before using the library. Concepts such as spawned processes and glob patterns versus regular expressions are not explained here.

This chapter is not meant to encourage the use of C or C++. Especially for Expect-like programming, working in Tcl is much, much easier than working with C or C++ and their usual edit-compile-debug cycles. Unlike typical compiled programs, most of the debugging of Expect programs is not getting the compiler to accept programs—rather, it is getting the dialogue correct. And this is much faster to do with Tcl.

If you are aware of the trade-offs between C and C++ and Tcl and have good cause to use this library, plow ahead. But if you do not know Tcl and simply want to avoid learning another language, I would suggest taking a step back, reexamining your decision, and giving Tcl a chance. It is more than meets the eye.

For many tasks, the Tcl environment is preferable to that of C and C++. However, C and C++ may be forced upon you if you already have a large amount of software written that uses some other mechanism to provide control over symbol tables and program flow.

For example, you might want to do Expect-like operations from another interpreter such as Basic, Lisp, or Perl. Adding Tcl to those interpreters is just going to make things more complicated. Instead, you can invoke Expect-like operations directly.

Rather than provide bindings for every interpreter under the sun, Expect comes with a library that can be linked with any program. The library has functions that can be used by hand, or with a little glue, added to any interpreter so they can be called in that new language.

In Chapter 22 (p. 512), I will describe how to produce standalone Expect binaries that still use Tcl for control. Often called compiled, such programs require no external scripts and are a convenient form to use if you want to package everything in a single file.

Overview

Calling Expect from C and C++ is straightforward. I will omit references to C++ from now on because most of the examples and explanations are identical in both environments. Expect comes as a library that you link with your other object files. The library contains three types of functions. These functions:

  • manipulate ptys and processes

  • wait for input using patterns

  • disconnect the controlling terminal

You need not use these functions together. For example, you can have Expect spawn a process, but then read from it directly (without using patterns). In addition, you can use functions out of the Tcl library (such as the regular expression pattern matcher) or the Expect extension library.

A number of facilities in the Expect program have no analog. Or to put it another way, the Expect library does not provide substitutes for all Expect commands. For example, there is no send function because you can use fprintf or write to do the same thing.

Here is a simple example to create and interact with a process. It may look confusing at first but it will make more sense as you read this chapter.

FILE *fp = exp_popen("chess");
exp_fexpectl(fp,exp_glob,"Chess
",0,exp_end);
fprintf(fp,"first
");

The first line runs the chess program. A FILE pointer is returned so that you can interact with the process. The exp_fexpectl function declares a glob-style pattern for which to wait. Finally, the last line sends a string back to the process.

Linking

Linking a program with the Expect library requires only naming the appropriate libraries. The Expect library is called libexpect.a and must be supplied when the program is linked. Assuming that it is installed in your system so that the linker can find it, the library is traditionally named from the command line as "-lexpect“.

For example, if your program is composed of the object files "foo.o" and "bar.o“, they can be linked together as:

cc foo.c bar.c -lexpect -ltcl

The Tcl library is listed here too. The Expect library “borrows” Tcl’s regular expression pattern matcher but nothing else. Rather than shipping yet another copy of the pattern matcher, it is easier just to link with the Tcl library. The Tcl library may be avoided entirely by supplying a replacement for the regular expression pattern matcher.

When the library is used with other languages, it may be desirable to replace the glob or regular expression pattern matchers with different ones. For example, another language may define regular expressions differently than Tcl. Using Tcl’s regular expressions with those of another language would be confusing. Unfortunately, there is no standard interface for pattern matchers. A simple solution is to replace calls to the pattern matchers with new calls that follow any of the interfaces used by the Expect library. Because these interfaces are likely to change, they are not formally documented. However, the current interfaces are relatively trivial and should be easily understandable to anyone familiar with popular UNIX pattern matchers.

Include Files

Any files that make references to the Expect library must include the following statement. The statement should appear before any Expect functions are called.

#include "expect.h"

This statement works for both C and C++ programs.

Some of the Expect library functions work with the C standard I/O package. If you use these parts of the library, you must also include that header as well.

#include <stdio.h>

If the compiler needs to be told where the Expect include files come from, add an appropriate argument to the compile command. For example, to compile foo.c, you might have to say:

cc -I/usr/local/include foo.c

The precise filename depends on where the include files have been installed. As before, the Tcl include files should also be available. Normally, both the Expect and Tcl include files live in the same directory, so one −i flag should suffice.

Ptys And Processes

The Expect library provides three functions to start new interactive processes. Each of them creates a new process so that its standard input, standard output, and standard error can be read and written by the current process.

exp_spawnl is useful when the number of arguments is known at compile time. exp_spawnv is useful when the number of arguments is not known at compile time. (The third function exp_popen will be described later.) In both cases, the arguments are passed literally—no shell pattern matching is done and no redirection occurs. The shell is simply not involved. I occasionally will refer to these functions generically as the spawn functions.

exp_spawnl and exp_spawnv parallel those of the UNIX functions execlp and execvp respectively. The calling sequences are as follows:

int
exp_spawnl(file, arg0 [, arg1, ..., argn] (char *)0);
char *file;
char *arg0, *arg1, ... *argn;

int
exp_spawnv(file,argv);
char *file, *argv[ ];

In both functions, the file argument is a relative or absolute file specification. No special character processing occurs (such as ~ or * expansion). exp_spawnl and exp_spawnv duplicate the shell’s actions in searching for an executable file from the list of directories associated with the PATH environment variable.

The argv parameter in exp_spawnv is made available to the new process as the argv parameter in main. exp_spawnl collects its remaining arguments and then massages them so that they also appear as the argv parameter in main. In both cases, the arguments are copied so that you can later change the pointers or what they point to without affecting the spawned process.

For example, the following command starts a telnet process to the SMTP port of uunet.uu.net:

fd = exp_spawnl("telnet",
            "telnet","uunet.uu.net","smtp",(char *)0);

Notice that the arg0 parameter is identical to the file parameter. Remember that the file parameter is not part of the argv array in the new main. argv[0] in the new process comes from arg0 in the current process.

In both exp_spawnl and exp_spawnv, the argument list must be terminated by (char *)0. Forgetting the terminating 0 is a common error and typically leads to a core dump.

If the functions are successful, they return a file descriptor. This file descriptor corresponds to the standard input, standard output, and standard error of the new process. You can use the write system call to write to the standard input:

write(fd,"foo
",4);

To read from the standard output or standard error, use the read system call.

read(fd,buffer,BUFSIZ);

A stream may be associated with the file descriptor by using fdopen. In almost all cases, you want to immediately unbuffer the new stream.

fp = fdopen(fd,"r+");
setbuf(fp,(char *)0);

If an error occurs during exp_spawnl or exp_spawnv, −1 is returned and errno is set appropriately. Errors that occur after a spawn function forks (e.g., attempting to spawn a non-existent program) are written to the standard error of the spawned process and are read by the first read. The rationale for this is described in Chapter 13 (p. 292).

The popen function in the C library accepts a shell command line, runs it, and returns a stream associated with it. Unfortunately, you can only choose to read from or write to a process. You cannot do both.

The Expect library defines exp_popen. It is styled after popen. exp_popen takes a Bourne shell command line and returns a stream that corresponds to the standard input, standard output, and standard error of the new process. Redirection and shell pattern matching are done on the command line. Unlike popen, exp_popen takes no type flag. popen uses a pipe which only supports one-way communication, but exp_popen uses a pty which supports two-way communication. Compare the declarations of popen and exp_popen:

FILE *popen(command, type)
char *command, *type;

FILE *exp_popen(command)
char *command;

The following statements spawn telnet. Some file arguments are listed and the standard error is redirected—not because this makes sense—but just to show that it can be done.

FILE *fp;
fp = exp_popen("telnet host smtp *.c 2> /dev/null");

Since exp_popen returns a stream, you use any of the standard I/O functions to access it. For example, the stream can be written to with fwrite, fprintf, fputc, and others. Here is an example with fprintf:

char *my_name = "Don";
fprintf(fp,"My name is %s
",my_name);

The stream can be read using fread, fscanf, fgetc, and others. Here is an example with fgets:

char buffer[100];
fgets(buffer,100,fp);

The actual implementation of exp_popen is defined in terms of exp_spawnl. It is shown below.

FILE *
exp_popen(program)
char *program;
{
    FILE *fp;
    int ec;

    ec = exp_spawnl("sh","sh","-c",program,(char *)0);
    if (0 > ec) return(0);

    fp = fdopen(ec,"r+");
    if (fp) setbuf(fp,(char *)0);
    return fp;
}

Several variables are made available by inclusion of expect.h. They should not be defined or declared but may be read and written.

Two of these variables are set as side-effects of the spawn functions. These are:

extern int exp_pid;
extern char *exp_pty_slave_name;

The exp_pid variable contains the process id of the process created by the spawn functions. The variable exp_pid is rewritten each time a spawn function is called so it should generally be immediately saved to another variable.

The spawn functions use a pty to communicate with the process. The variable exp_pty_slave_name is the name of the slave side of the pty associated with each spawned process. Put another way, exp_pty_slave_name is the name of the tty that the spawned process uses for its standard input, standard output, and standard error.

Here is a program to spawn the cat program and print out the process id and tty name of the spawned process.

#include <stdio.h>
#include "expect.h"

main(){
    FILE *fp = exp_popen("cat");
    printf("pid = %d
",exp_pid);
    printf("pty name = %s
",exp_pty_slave_name);
}

When run on my system, this program printed:

pid = 18804
pty name = /dev/ttyp3

Several other variables control aspects of the spawn functions. They are:

extern int exp_console;
extern int exp_ttyinit;
extern int exp_ttycopy;
extern char *exp_stty_init;

By default, the pty is initialized the same way as the user’s tty (if possible, i.e., if the environment has a controlling terminal.) This initialization is performed only if the variable exp_ttycopy is nonzero. It is nonzero by default.

The pty is further initialized to a system-wide default if the variable exp_ttyinit is nonzero. The default is generally comparable to "stty sane“. exp_ttyinit is nonzero by default.

The tty setting can be further modified by setting the variable exp_stty_init. This variable is interpreted in the style of stty arguments. For example, the following statement repeats the default initialization. If exp_stty_init is set to 0, no extra initialization is performed.

exp_stty_init = "sane";

These three initializations may seem like overkill, but they solve a number of problems. The rationale for all this is described in Chapter 13 (p. 296).

The variable exp_console attempts to associate the new pty with the console. If the association is made successfully, any messages to the console are sent to the pty and can be read as the output of the process.

If your system supports the environ variable, you can use this to control the environment of the spawned process. It should be declared as:

extern char **environ;

The environ variable is an array of character pointers to strings representing the environment variables. (This representation is described in detail in most C texts.) When a new process is spawned, the environ array is copied into the new process and becomes its environment. You may modify the environ table before spawning a process, and the spawned process will get the modified environment.

Most other attributes of a process are inherited according to the “usual” rules of the exec family of functions. This includes things such as user id, group id, current working directory, etc. Signals that are caught are reset to the default action. Lastly, the process is placed in a new process group with a new session id.

It is possible to change attributes in the context of the child process just before the new program is given control (via an exec function) by providing a definition for exp_child_exec_prelude. For example, you might want the child to ignore SIGHUP and SIGTERM. This could be done as follows:

void exp_child_exec_prelude() {
    signal(SIGHUP, SIG_IGN);
    signal(SIGTERM, SIG_IGN);
}

Allocating Your Own Pty

By default, a pty is automatically allocated each time a process is spawned. It is possible to allocate a pty through some other mechanism (of your own). Conceivably, you could also use a pair of fifos or something similar even though it may not completely emulate tty functionality.

Two variables control pty allocation. They are:

extern int exp_autoallocpty;
extern int exp_pty[2];

The variable exp_autoallocpty is set to one by default. If you set it to zero, a pty is not automatically allocated by the spawn functions. Instead, the value of exp_pty[0] is used as the master pty file descriptor, and the value of exp_pty[1] is used as the slave pty file descriptor.

The following illustrates pty allocation with the pipe system call. (On traditional UNIX systems, a pipe is a one-way device, so this example is not suitable for most Expect applications. Nonetheless, it serves to demonstrate the calling protocol.) The first statement turns off the automatic pty allocation. The second statement uses the pipe system call which conveniently produces two connected file descriptors in the exp_pty array. The exp_popen creates a cat process and uses the two file descriptors in the exp_pty array.

exp_autoallocpty = 0;
pipe(exp_pty);
exp_popen("cat");

When you allocate your own pty, you must also initialize it. The spawn functions do none of the usual pty initializations (e.g., exp_stty_init is not used).

After the new process is created, the slave pty file descriptor is closed in the current process and the master pty file descriptor is closed in the spawned process. In the context of the current process, all further communication takes place with the master pty file descriptor (i.e., exp_pty[0]).

Whether or not you allocate your own pty, the new process may need to close file descriptors. By default, all file descriptors to processes created by the spawn functions are marked close-on-exec. This enforces the behavior described in the previous paragraph of closing the master pty file descriptor in the spawned process. Other non-spawn-related file descriptors should also be marked close-on-exec so that they can be closed automatically. Alternatively, the function pointer exp_close_in_child may be set to a function that closes additional file descriptors. By default, exp_close_in_child is 0.

void (*exp_close_in_child)();

When using Tcl (with or without Expect), the function exp_close_tcl_files can be used to close all the files above the standard input, standard output, and standard error to the highest descriptor that Tcl knows about. This is exactly what Expect does. The following statement enables this behavior.

exp_close_in_child = exp_close_tcl_files;

This behavior is rather crude but often sufficient. A more sophisticated solution requires delving into the Tcl internals and is beyond the scope of this book.

Closing The Connection To The Spawned Process

The Expect library provides no special functions to close the connection to a spawned process. Generally, it is sufficient to call close. If you have converted the file descriptor to a stream (or used exp_popen which returns a stream), call fclose instead.

Once the process exits, it should be waited upon in order to free up the process slot. When convenient, you can wait for the process using any of the wait family of calls. You can also catch SIGCHLD before waiting, or you can ignore SIGCHLD entirely. Further discussion on this can be found in any UNIX system programming text.

As described in Chapter 4 (p. 103), some processes do not automatically terminate when their standard input is closed. You may have to send them explicit commands to exit, or alternatively you can kill them outright. As described above, the exp_pid variable provides the process id of the most recently spawned process.

There is no matching exp_pclose to exp_popen (unlike popen and pclose). It only takes two functions to close down a connection (fclose followed by waiting on the process id), but it is not uncommon to separate these two actions by large time intervals, so providing a new function for this purpose is of little value. Just close the stream using fclose and wait for the process.

Expect Commands

The library provides functions that can be used to read files or streams. Like the Expect program’s expect command, the library functions wait for patterns to appear or special events to occur.

There are four functions to do expect-like processing. Two are for handling file descriptors, and the other two are for streams. One of each take lists of arguments similar to exp_spawnl while the others take a single variable argument descriptor similar to exp_spawnv. I will occasionally refer to these functions generically as the “expect functions”.

Table of expect functions—each function is prefixed with "exp_

Figure 21-1. Table of expect functions—each function is prefixed with "exp_

The names are mnemonic. Like exp_spawnl and exp_spawnv, the expect functions that end with an "l" take arguments lists, and those ending with "v" take a single variable descriptor. An "f" means that the function reads a stream; otherwise it reads from a file descriptor.

The table shows the short names but these are further prefaced by the string "exp_“.[70] For example, the exp_expectl function takes an argument list and reads from a file descriptor. A simple example of exp_expectl is:

exp_expectl(fd, exp_glob, "prompt*", 1, exp_end);

This call waits for a prompt to arrive from the file descriptor fd. exp_glob is the pattern type. It says that the pattern "prompt*" is a glob pattern. When it arrives, exp_expectl returns the value 1.

More patterns can be expected at the same time by adding them to the argument list.

exp_expectl(fd, exp_glob, "prompt*", 1,
                exp_glob, "another pattern", 2,
                exp_glob, "and another", 3,
                exp_end);

The end of the arguments is always marked by exp_end. The objects exp_end and exp_glob are predefined constants of type "enum exp_type“. This is automatically defined when you include expect.h. The public definition of exp_type follows. I will mention the other values shown here later on.

enum exp_type {
    exp_end,       /* placeholder - no more cases */
    exp_glob,      /* glob-style */
    exp_exact,     /* exact string */
    exp_regexp,    /* regexp-style, uncompiled */
    exp_compiled,  /* regexp-style, compiled */
    exp_null,      /* matches binary 0 */
};

The value returned by exp_expectl is the number following the pattern that matches. The choice in this example of 1, 2, and 3 is arbitrary. You can associate the same numbers with multiple patterns. In actual code, it is a good idea to use preprocessor definitions to hide the numeric values unless they have some inherent meaning.

Here is an example that looks for a successful login such as from a telnet dialogue. When a value is returned, the switch statement passes control to an appropriate case. Notice that this example uses macros to hide the real values of the number to be returned. This makes the statement much more readable since each value is used in two places—once in the expect function and once in the case statement.

switch (exp_expectl(
    exp_glob,"connected",CONN,
    exp_glob,"busy",BUSY,
    exp_glob,"failed",ABORT,
    exp_glob,"invalid password",ABORT,
    exp_end)) {
case CONN:                /* logged in successfully */
    break;
case BUSY:                /* couldn't log in at the moment */
    break;
case ABORT:                /* can't log in at any moment! */
    break;
case EXP_TIMEOUT:
    break;
}

If the expect function times out, it returns EXP_TIMEOUT. Notice that this does not appear in the pattern list. Unlike the Expect program, there is no need to ask for the timeout to be handled. The expect functions do not automatically execute actions—they simply describe what happened. So even though you did not ask for it, you cannot miss the timeout.

The number of seconds in the timeout is determined by the integer variable exp_timeout. If exp_timeout is −1, the timeout is effectively infinite and will never occur. A timeout of 0 is used for polling and is described further on page 501.

There are three other special values that can be returned. EXP_EOF is returned upon eof. −1 is returned if a system call failed or something otherwise horrible occurred. For example, if an internal memory allocation fails, −1 is returned and errno is set to ENOMEM. errno will always be set if −1 is returned.

If the integer variable exp_full_buffer is nonzero, then EXP_FULLBUFFER is returned when the expect function’s buffer is full. If exp_full_buffer is zero and the buffer is full, the first half of the buffer is dropped, the second half of the buffer is copied down, and the expect function continues. The buffer is described further on page 498.

All of the special values are small negative integers, so it is a good idea to associate patterns with positive integers although there is nothing in the code that enforces this.

Regular Expression Patterns

Regular expressions can be identified with exp_regexp. Here is the first example from page 494, rewritten to use a regular expression pattern:

exp_expectl(fd, exp_regexp, "prompt.*", 1, exp_end);

The type of all patterns are always identified explicitly, so different pattern types can be mixed without confusion. Here is an expect call with both glob and regular expression patterns:

exp_expectl(fd ,exp_regexp, "prompt.*", 1,
                exp_glob, "another pattern", 2,
                exp_regexp, "and another", 3,
                exp_end);

Caching Regular Expressions

The regular expression implementation used by Expect converts the pattern to an internal form that allows strings to be tested very quickly. The conversion procession is known as compilation. The compilation itself can cost more in terms of time than is saved later during the pattern matching. But if the pattern is going to be used more than once, compilation can ultimately save a great deal of time.

In the examples so far, the expect functions compile the regular expressions internally. Because input usually arrives slowly, patterns get evaluated many times and the compilation process pays off and time is saved. However, the compiled form is discarded at the end of each expect function. If the function is in a tight loop, this can be wasteful.

You can pass the compiled form to the expect functions by using exp_compiled instead of exp_regexp. Assuming the compiled form is stored in fastprompt, the earlier example might be rewritten this way:

exp_expectl(fd, exp_compiled, "prompt.*", fastprompt, 1,
    exp_end);

The string-style pattern is still passed, but it is only used for bookkeeping and debugging. The actual pattern matching uses the compiled form.

Patterns are compiled using the function TclRegComp. It takes a pattern and returns the compiled form which is of type pointer to regexp (a typedef). Here is what it might look like when used in a loop:

#define PROMPTED 17

char *pat = "prompt.*";
regexp *fastprompt = TclRegComp(pat);

while (1) {
    switch (exp_expectl(fd,
        exp_compiled, pat, fastprompt, PROMPTED,
        exp_end))
    case PROMPTED:
        /* respond to prompt */
    case . . .
}

Use free to free the memory used by the compiled form when it is no longer needed.

free((char *)fastprompt);

Malformed patterns cannot be compiled. If TclRegComp returns 0, compilation failed. The variable tclRegexpError contains an error message describing the problem. Expressed in C, this looks like:

fastprompt = TclRegComp(pat);
if (fastprompt == NULL) {
    fprintf(stderr, "regular expresion %s is bad: %s",
            pat, tclRegexpError);
}

Exact Matching

The pattern type exp_exact identifies a pattern that must be exactly matched in the input. The usual C escapes must be observed; however, no characters are interpreted as wildcards or anchors. Exact patterns are unanchored.

exp_expectl(fd, exp_exact, "#*!$", 1, exp_end);

The example above returns 1 only if the character stream contains the consecutive sequence of characters "#“, "*“, "!“, and "$“.

Matching A Null

By default, nulls (bytes with value zero) are automatically stripped from the spawned process output. This can be disabled by setting the integer variable exp_remove_nulls to 0 and reenabled by setting it to 1.

Once null stripping is disabled, nulls can be matched using exp_null. A string-style pattern is still passed, but it is only used for bookkeeping and debugging. For example:

exp_expectl(fd,
    exp_null, "zero byte", 1,
    exp_end);

What Characters Matched

Figure 21-2. 

When an expect function returns, the variable exp_buffer points to the buffer of characters that were being considered for a match. exp_buffer_end points to one character past the end of the buffer. The buffer is null-terminated. If a pattern matches, the variable exp_match is set to point into the same buffer but at the position where the pattern first matches. The variable exp_match_end points to one past the last matching character. All of these variables are character pointers.

char *exp_buffer;
char *exp_buffer_end;
char *exp_match;
char *exp_match_end;

Parenthesized subpatterns from regular expressions have their match information saved but only if the compiled form is used. Each regexp object includes the following members:

#define NSUBEXP 10

char *startp[NSUBEXP];
char *endp[NSUBEXP];

Each subpattern match is defined by a startp and endp pair. startp points to the start of the matching string, and endp points to one past the end of the matching string. startp[0] and endp[0] are identical to exp_match and exp_match_end. The remaining indices correspond to the parenthesized subpatterns in the original pattern. startp is set to 0 if the subpattern did not match.

For example, here is a fragment to print out all of the match information. In the loop, the submatch is temporarily null-terminated so that it can be printed. (The endp pointers are always guaranteed to point to writeable memory.) In order to avoid corrupting the string, the character where the null is to be written is temporarily saved and then restored.

exp_expectl(fd,
        exp_compiled, pattern, regexp, 1,
        exp_end);
for (i=0;i<NSUBEXP;i++) {
    char save;

    if (regexp->startp[i] == 0) continue;

    /* temporarily null-terminate the match */
    save = regexp->endp[i];
    regexp->endp[i] = '';

    printf("match [%d] = %s
",i,regexp->startp[i]);

    /* restore old character */
    regexp->endp[i] = save;
}

The expect functions automatically allocate space for exp_buffer as required. The variable exp_match_max is an integer that describes the maximum length of a string guaranteed to match. By default, exp_match_max is 2000.

When The Number Of Patterns Is Not Known In Advance

The exp_expectl function is appropriate when the list of patterns is known in advance. At a minimum, the number of patterns must be known in advance.

When the number of patterns can vary, the function exp_fexpectv is more suitable. This function is called with only two arguments. The first is a file descriptor. The second is an array of pattern descriptors. The prototype is:

int exp_expectv(int fd, struct exp_case *pats);

struct exp_case is defined as follows:

struct exp_case {
    char *pattern;
    regexp *re;
    enum exp_type type;
    int value;
};

The information in an exp_case structure is exactly the same information that was passed as the direct arguments in exp_expectl. The pattern is stored in pattern. An optional compiled regular expression is stored in re. The type element describes the type of pattern and is an exp_type enumeration constant (see page 495). As before, the final pattern type must be exp_end. Finally, value is the integer returned when the associated pattern matches.

exp_expectv works slightly differently than exp_expectl when the pattern type is exp_regexp. In this case, exp_expectv compiles each pattern and stores the compiled form in re. The compiled form is left accessible in the exp_case structure for your use or reuse if exp_expectv is recalled with the same patterns. If the type is exp_regexp, then exp_expectv checks if re is initialized before compiling the pattern. The pattern is compiled only if re is not initialized.

When you are done with the regular expression, you must free re in each exp_case that had a regexp, whether you or exp_expectv compiled it.

Expecting From Streams

Both exp_expectl and exp_expectv have analogous functions that work on streams instead of file descriptors. The stream functions have the same names as their file counterparts except that the stream functions have an "f" in the name. This is similar to the distinction between write and fwrite. Both of the stream versions are identical to their file counterparts except that the first argument is a stream instead of a file descriptor.

exp_fexpectl is the stream version of exp_expectl. A simple example looks like this:

FILE *fp = exp_popen("telnet");
exp_fexpectl(fp, exp_glob, "prompt*", 1, exp_end);

On some systems, the stream versions of the expect functions are much slower than the file descriptor versions because there is no way to portably read an unknown number of bytes without the potential of timing out. Thus, characters are read one at a time. While automated versions of interactive programs do not usually demand high speed, the file descriptor functions are likely to be more efficient on all systems.

You can get the best of both worlds, writing with the usual stream functions (i.e., fprintf) and reading with the file descriptor versions of expect, as long as you do not attempt to intermix other stream input functions (e.g., fgetc). To do this, pass "fileno(stream)" as the file descriptor to exp_expectl or exp_expectv. Fortunately, there is little reason to use anything but the expect functions when reading from interactive programs.

Running In The Background

In Chapter 17 (p. 369), I described how to move a process into the background after it had begun running. A typical use for this is to read passwords and then go into the background to sleep before using the passwords to do real work.

Moving a process into the background is tricky. It also differs from system to system. Fortunately, Expect incorporates this same functionality inside of the spawn routines. Because moving processes into the background is such a common task for programs that use the Expect library, it is available through a separate interface.

To move a process into the background, fork a process, call exp_disconnect in the child process, and then call exit in the parent process. Here is code to do this:

    switch (fork()) {
    case 0: /* child */
        exp_disconnect();
        break;
    case −1: /* error */
        perror("fork");
    default: /* parent */
        exit(0);
    }

Calling exp_disconnect disassociates the process from the controlling terminal. If you wish to move a process into the background in a different way, you must set the integer variable exp_disconnected to 1. (Initially it is 0.) This allows processes spawned after this point to be started correctly. The exp_disconnect function sets exp_disconnected to 1.

int exp_disconnected;

exp_disconnected is also shared with the Expect program. If you invoke Expect’s disconnect command, it will also set exp_disconnected to 1.

Handling Multiple Inputs And More On Timeouts

In some cases, you do not want to wait inside of an expect function. Instead, you wait in select, poll, or an event manager such as those provided by window systems. In this case, give the file descriptor corresponding to the spawned process to the event loop. When the event loop detects that input can be read from the file descriptor, it calls back in some fashion, after which you can call an expect function to test the patterns. You can guarantee that the expect function returns immediately by setting exp_timeout to 0. If none of your patterns match, EXP_TIMEOUT is returned.

Here is an example using the select system call. select is directed to watch the file descriptor in fd. select returns when it finds that there is data to read. exp_expectl is then called and it reads the data and performs pattern matching. If none of the patterns match, exp_expectl returns EXP_TIMEOUT immediately rather than attempting to read more data.

#define WAIT_FOREVER (struct timeval *)0

FD_SET(fd,&rdrs);
select(fd_max, &rdrs, . . ., WAIT_FOREVER);

exp_timeout = 0;
exp_expectl(fd, . . ., exp_end);

If exp_timeout is nonzero, the expect function can block in the read system call while reading from a single file descriptor. Internally, an ALARM signal is used to interrupt the read. If you define signal handlers, y can choose to restart or abort the read. The integer variable exp_reading is 1 if and only if the read has been interrupted and 0 otherwise. The following statement aborts the read:

longjmp(exp_readenv, EXP_ABORT);   /* abort the read */

The read is restarted as follows:

longjmp(exp_readenv, EXP_RESTART); /* restart the read */

Output And Debugging Miscellany

Some output and debugging controls exist in the library. The variety and flexibility of these are not great because there is not a lot of demand for more development in this area. For instance, interaction debugging is usually done using Tcl, not C.

The controls that exist parallel the commands in the Expect program and extension. They are manipulated using the following variables. All are 0 by default.

int exp_loguser;
int exp_logfile_all;
FILE *exp_logfile;
int exp_is_debugging;
FILE *exp_debugfile;

If exp_loguser is nonzero, the expect functions send any output from the spawned process to the standard output. Since interactive programs typically echo their input, this usually suffices to show both sides of the conversation.

If exp_logfile is also nonzero, this same output is written to the stream defined by exp_logfile.

If exp_logfile_all is nonzero, exp_logfile is written regardless of the value of exp_loguser.

Debugging information internal to Expect is sent to the standard error when exp_is_debugging is nonzero. The debugging information includes every character received and every attempt made to match the current input against the patterns. In addition, nonprintable characters are translated to a printable form. For example, a control-C appears as a caret followed by C. If exp_logfile is nonzero, this information is also written to exp_logfile.

If exp_debugfile is nonzero and set to a stream pointer, all normal and debugging information is written to that stream, regardless of the value of exp_is_debugging.

All of these variables directly control their counterparts in the Expect program and extension. For example, the Expect command "log_user 1" sets the value of exp_loguser to 1.

Pty Trapping

Some systems (notably HPs) require that ptys be trapped in order to detect an eof through select or poll. When trapping is enabled, all ioctls performed by the spawned process on the pty must be acknowledged. This acknowledgment is normally performed automatically when Expect is in one of its expect functions. But occasionally, you may need to explicitly deal with trapping. For example, you might want to change the mode of the slave’s pty after it has been started.

The trap and acknowledgment protocols are described in the documentation for your system. I will not describe them here because they can be avoided. This is fortunate, not because they are complex but because they cannot be performed while you are doing something else (e.g., in the middle of an ioctl call). The solution is to temporarily disable the trapping.

Trapping can be controlled with exp_slave_control. The first argument is the file descriptor corresponding to the spawned process. The second argument is a 0 if trapping is to be disabled and 1 if it is to be enabled.

/* disable trapping */
exp_slave_control(fd,0);

/* fiddle with mode of pty */
. . .

/* enable trapping */
exp_slave_control(fd,1);

On systems which do not use trapping, exp_trap_control turns into a no-op. Thus, if you are concerned about portability to systems which require trapping, use the trap control function.

Exercises

  1. Write a program using the Expect library and then rewrite it using the Expect program. Compare the time it took you to write (and debug) both. Compare the size of your source. Compare the size of the resulting executables. What can you conclude from these comparisons? Repeat this exercise on a significantly larger example.

  2. Create a library specifically optimized for ftp. It should contain functions to start and stop ftp, and to send and expect ftp requests. How much simplification can be made over the original expect functions?

  3. Create a terminal-emulator widget for Tk. What are the advantages and disadvantages between such a widget and the approach shown in Chapter 19 (p. 443).



[70] In fact, everything in the library is prefaced with "exp_“.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.108.9