Chapter 11. Handling Multiple Processes Simultaneously

In the previous chapter, I introduced the concept of a spawn id and how the spawn_id variable could be used to change the attention of Expect commands between multiple processes. In this chapter, I will demonstrate a mechanism that provides a more explicit way of denoting the current spawn id. Explicitly naming spawn ids makes it possible to handle multiple spawn ids in the same command.

I will also cover the expect_before and expect_after commands, which can greatly simplify scripts by performing common tests (such as for eof and timeout) in only a single command of the script.

Implicit Versus Explicit Spawn Ids

The previous chapter demonstrated various ways of interacting with two processes, an ftp process and a write process. By setting the variable spawn_id, the send and expect commands can communicate with either process. Here is an example of that from the previous chapter:

set spawn_id $ftp
send "get $file1
";                            expect "220*ftp> "

set spawn_id $write
send "successfully retrieved file
"

set spawn_id $ftp
send "get $file2
";                            expect "220*ftp> "

It is also possible to supply send and expect with an explicit parameter representing a spawn id. In this case, the commands do not use the spawn_id variable. Instead the spawn id is passed as an argument following the flag "-i“. For example:

send -i $write "successfully retrieved file
"

This command sends the string to the write process. The value of spawn_id is irrelevant, as send uses the value following the −i flag. The value of spawn_id remains what it was before the command.

Using this line, you can rewrite the earlier fragment as:

set spawn_id $ftp
send "get $file1
";                            expect "220*ftp> "

send −i $write "successfully retrieved file
"

send "get $file2
";                            expect "220*ftp> "

The "send -i" sends the string to the write process while all the other commands communicate with the ftp process.

Using −i is convenient when the script only has to send one thing to another process while a lot of interaction is occurring with the currently spawned process.

The −i flag is also supported by the expect command. The expect commands in the previous example could have been written:

expect −i $ftp "220*ftp> "

If multiple patterns are used, they are all tested against the output from the spawn id following the −i flag. In the following example, expect executes action if either of the 220 or 550 codes are returned by the ftp process.

expect {
    -i $ftp
    "220*ftp> "                        action
    "550*ftp> "                        action
}

Notice how the braces enclose all of the arguments including the −i flag. Patterns can be specified on the same line as the −i flag. Do whatever you think is most readable. Here is another rendition of the command. It looks different but does the same thing.

expect {
    -i $ftp "220*ftp> "   action
    "550*ftp> "           action
}

All of the other expect flags and keywords work with −i as well. The following example shows two regular expressions, the timeout keyword and the exp_continue action.

expect {
    -i $ftp -re "2(5|0) .*ftp> "    {action1}
    -re "220-.*ftp>"                    {
                exp_continue
    }
    timeout
}

Many of the commands in Expect support the −i flag in the same way as send and expect. Other commands supporting −i include close, interact, match_max, parity, remove_nulls, and wait.

Waiting From Multiple Processes Simultaneously

By using multiple −i flags in a single expect command, it is possible to wait for different processes simultaneously. The following fragment executes ftp_action if an ftp process sends its prompt, or it executes the shell action if the shell sends its prompt.

expect {
    -i $ftp "ftp> " {ftp_action}
    -i $shell $prompt {shell_action}
}

At most one of the actions can be executed. After the action, control passes to the next line in the script. This is very similar to prior expect commands.

There is an input buffer associated with each spawn id. So any output from ftp is kept separate from that of the shell. Of course, when the output(s) appear on the terminal, they are mixed together, but that is just what you would see at the terminal yourself while running multiple processes simultaneously. Usually, this is not a problem. But if you do not want to see the outputs mixed together, expect one spawn id at a time. Alternatively, use log_user 0 to disable the normal output, and then explicitly write the output by calling "send_user expect_out(buffer)" when convenient.

If some but not all of the ftp prompt appears and the entire shell prompt appears, shell_action will execute. The beginning of the ftp prompt will remain in the input buffer for the next expect command.

Patterns corresponding to a single −i flag are matched sequentially as before. Because multiple processes are never active simultaneously, patterns with different −i flags are not ordered. Consider the next example:

expect {
    -i $proc1 "pat1x" act1x
              "pat1y" act1y
    -i $proc2 "pat2x" act2x
}

In this command, there are two patterns for proc1 and one for proc2. While the expect command is waiting, if proc1 produces output, it is examined first for pat1x and then for pat1y. If proc2 produces output, it is examined for pat2x. Whichever process produces output first determines which of the sets of patterns is tested first.

Example—Answerback

At login time, some systems attempt to figure out the type of terminal being used. This is done by a technique called answerback. The system queries the terminal by sending it a special escape sequence. Instead of printing the escape sequence on the screen, the terminal responds by returning an identification code describing what type of terminal it is.

The fragment below telnets to a host, logs in, and then begins interacting with a program. What happens if the systems asks the terminal to identify itself?

spawn telnet hostname
expect "name: " {send "$username
"}
expect "word: " {send "$password
"}

# possible answerback occurs here

expect $prompt {send "$program
"}
. . .

After the password, the script waits for the prompt. Instead, the system sends the request for identification sequence. Expect handles this just like any other output from the process—it echoes the request to the standard output and continues looking for the prompt. Now the terminal intercepts the escape sequence and responds with its identification code. The identification code appears to Expect on the standard input, just as if the user had typed it. Unfortunately, the Expect script is not watching the standard input. So the script continues to wait, the system also waits, and the user fumes.

Here is a better version:

spawn telnet hostname
expect "name: " {send "$username
"}
expect "word: " {send "$password
"}

# handle possible answerback here

stty raw -echo
expect {
    "$prompt"     {send $program
}
    -i $user_spawn_id -re ".+" {
        send $expect_out(buffer)
        exp_continue
    }
}
stty -raw echo
. . .

This script starts out the same way, but while looking for the prompt, the script also watches for characters coming from the standard input. There is no need to recognize the precise response (or request for that matter). Whatever characters are sent back must be the response, so ".+" (which matches any sequence of one or more characters) is sufficient. exp_continue forces the expect to repeat. If more characters appear from the standard input, they are also sent back. Eventually the prompt appears and control passes to the next command in the script.

If the system stops requesting the terminal identification or (more likely) the same script is used on a system where no identification is required, the script still works. It is even conceivable that the system interacts in a very complex way with the terminal, perhaps responding to the terminal identification by asking for further information. It would not be possible to write the script while only listening to one source of information at a time.

The expect is surround by two stty commands. The first stty command has the arguments raw and -echo. Without the raw argument, the terminal would have to end its response with a carriage-return—which it does not do. The -echo disables echoing so that the response is not echoed back to the terminal. The second stty simply undoes the effect of the first stty. In Chapter 15 (p. 336), I will describe a shorter way of writing this same script.

Here is the main body of the chess script from the previous chapter, rewritten to accept input from either source at any time. While the real game of chess does not benefit from this rewrite, it demonstrates that the script can be written without regard to whether moves alternate strictly or not.

while 1 {
    expect {
        -i $id2 -re "\.\. (.*)
" {
            send −i $id1 $expect_out(1,string)
        }
        -i $id1 -re "\.\. .*\. (.*)
" {
            send −i $id2 $expect_out(1,string)
        }
    }
}

Which Pattern Goes With Which Spawn Id

In the chess script, it is very clear which pattern associates with which spawn id—any pattern immediately preceded by a "−i" flag is associated with the spawn id that follows. If additional patterns are specified before another "−i" flag, the additional patterns are associated with the most recently specified spawn id. Here is an example:

expect {
    -i $id1
    "patternX"   actionX
    "patternY"   actionY
    -i $id 2
    "patternZ"   actionZ
}

In the fragment above, expect waits for either patternX or patternY from spawn id $id1, or patternZ from $id2.

Patterns that appear before any −i flags are associated with the currently spawned process, described by the contents of the spawn_id variable. This is exactly what was done earlier in the telnet example.

The keyword pattern eof works just like any other pattern associated with the most recent spawn id. Here is the chess script augmented with eof patterns.

while 1 {
    expect {
        -i $id2 -re ".. (.*)
" {
            send −i $id1 $expect_out(1,string)
        } eof exit
        -i $id1 -re ".. .*. (.*)
" {
            send −i $id2 $expect_out(1,string)
        } eof exit
    }
}

In this example, the first "eof exit" is associated with id2 and second "eof exit" with id1. It is possible to associate the same pattern with multiple spawn ids. I will show how to do that shortly.

The timeout keyword works as before; however, it is worth pointing out that the timeout does not associate with any particular spawn id. So it does not matter where you put the timeout keyword (or even if you leave it off entirely). Here is a previous fragment written with a timeout pattern.

set timeout 15
expect {
    timeout timeout_action
    -i $id1
    "patternX" actionX
    "patternY" actionY
    -i $id 2
    "patternZ" actionZ
}

This fragment times out after 15 seconds if none of the patterns match from either spawn id $id1 or $id2. Even though there is no −i flag preceding timeout, the pattern does not associate with the currently spawned process. Timeouts do not associate with processes.

For the same reason, it does not make sense to have two different timeouts. The shortest one will always win, after all. However, there are certainly algorithms for which it does make sense to wait different amounts of times for input from different sources. In Chapter 15 (p. 338), I will address this situation using the interact command.

Which Spawn Id Matched

Except for timeout, patterns are always associated with a particular spawn id. You have already seen cases where actions are completely different. A common technique is to pass the spawn id itself to each action as a parameter, reusing the same action.

The following fragment runs a command on the fastest of three systems.

spawn rlogin $host1; set spawn_id1 $spawn_id
spawn rlogin $host2; set spawn_id2 $spawn_id
spawn rlogin $host3; set spawn_id3 $spawn_id
expect {
    -i $spawn_id1 $prompt {work $host1}
    -i $spawn_id2 $prompt {work $host2}
    -i $spawn_id3 $prompt {work $host3}
}

There is no explicit comparison to find which is fastest. Rather, it is implicitly derived just by virtue of it being the first to complete the rlogin sequence and return a prompt.

When the first of the three systems returns the prompt, the winning spawn id is passed to the procedure work which does the actual interacting. The remaining spawn ids are simply ignored.

The technique demonstrated here can certainly be refined. For example, the systems could all have their load average checked, memory usage checked, etc., but the general idea extends to other applications involving multiple processes being controlled. As an aside, the simplistic use of rlogin as a test for speed is in many cases quite accurate. Both the initial network connection and login sequences themselves are fairly high-overhead operations, and serve as good indicators of subsequent response time for many interactive programs.

This example can be simplified. The expect command records the matching spawn id in expect_out(spawn_id) before executing an action. This value can be stored into spawn_id itself or used as the argument to a −i flag. The following fragment passes the spawn id as an argument to another procedure. This version of the work procedure is especially designed to expect the spawn id as a parameter.

expect {
    -i $host1 $prompt {}
    -i $host2 $prompt {}
    -i $host3 $prompt {}
}
work $expect_out(spawn_id)

This method avoids having to write a different action for each spawn id. Conceivably, the work procedure could even refer (perhaps by upvar or global) to expect_out so that a parameter is not even necessary. In the next section, I will show how to simplify this even further.

Spawn Id Lists

It is possible to associate a single action with the multiple spawn ids simultaneously. This is accomplished by using a list of spawn ids as an argument to the −i flag. The previous expect command could be written as:

expect {
    -i "$host1 $host2 $host3" $prompt {
        work $expect_out(spawn_id)
    }
}

This syntax is just a generalization of the earlier syntax. For example, additional patterns can be added, in which case they also refer to the three processes. The following adds on a test that catches eof from any of the processes.

expect {
    -i "$host1 $host2 $host3" $prompt {
        work $expect_out(spawn_id)
    }
    eof exit
}

More −i flags can be used as before. For example, if you wanted to look for the prompt from the three processes, and also the possibility of a special pattern from just host1, you could write that as:

expect {
    -i "$host1 $host2 $host3" $prompt {
        work $expect_out(spawn_id)
    }
    eof exit
    -i $host1 another-pattern {host1-action}
}

If you want, you can put all −i arguments in double quotes when using a single spawn id. The quotes are only absolutely necessary when using lists. Do not use braces since they force the $ to be interpreted literally.

expect −i "$host1 $host" pattern   ;# OK
expect −i "$host1" pattern         ;# OK
expect −i $host1 pattern           ;# OK
expect −i {$host1} pattern         ;# WRONG!

Example—Connecting Together Two Users To An Application

The following fragment is one possible way of writing the kernel of kibitz, an Expect script that connects together two users and an application (such as a shell). Both users can type to the application, and both users see the results. This is very useful for consulting or group editing.

Here, app is the spawned process shared between the users. One user is connected to the standard input and the other user is referred to by the spawn id user2.

expect {
    −i "$user_spawn_id $user2" -re ".+" {
        send −i $app $expect_out(buffer)
        exp_continue
    }
    −i $app -re ".+" {
        send_user -raw $expect_out(buffer)
        send −i $user1 $expect_out(buffer)
        exp_continue
    }
}

The script waits for input from both users and the application, all at the same time. The .+ pattern in each case allows the script to process as many characters as arrive. Actual processing is simple. Characters from either user are sent to the application. Characters from the application are sent to both users.

No code is necessary to send the keystrokes of one user to the other for echoing purposes. The application takes care of all the echoing automatically. For example, a program such as a shell normally echos typed characters back to the user. In this case, the script ends up sending them to both users. So both users see what each other types.

Each action ends by executing exp_continue. This is more verbose than wrapping the entire expect in a while loop, but it is more efficient. By remaining in the expect after each action, no time is spent reparsing the expect command each time. The parsing time is insignificant in all but the most CPU-intensive applications.

In Chapter 16 (p. 351), I will show how to rewrite the kibitz loop in an even more compact and efficient form.

Example—Timing All Commands

In Chapter 7 (p. 176), I showed how to emulate the UNIX script command which records all input and output in an interactive session. Suppose you want a record of just the lines input by the user along with a timestamp showing when each was entered. That can be done with the following script called timeallcmds. It is split into two parts: initialization and a loop. Here is the initialization:

log_user 0
spawn $env(SHELL)
stty raw -echo
set timeout −1
set fp [open typescript w]

Logging is turned off and the terminal is put into no-echo mode. This allows the script complete control over all output that the user sees. The terminal is put into raw mode. The timeout is disabled. Finally, a log file is created. It is called typescript just as in the UNIX script command.

Once the initialization is complete, the script loops executing an expect command repeatedly. The expect command waits for characters from either the user or the process. Characters from the process get sent to the user so that they can be immediately seen. Characters from the user are logged and sent on to the process.

expect {
    -re ".+" {
        send −i $user_spawn_id $expect_out(buffer)
        exp_continue
    }
    eof exit
    −i $user_spawn_id -re "(.*)
" {
        send −i $spawn_id $expect_out(buffer)
        puts $fp $expect_out(1,string)
        puts $fp [exec date]
        exp_continue
    }
    -re ".+" {
        send −i $spawn_id $expect_out(buffer)
        puts -nonewline $fp $expect_out(buffer)
        exp_continue
    }
}

Two patterns are used to wait for characters from the user. One accepts strings terminated with a return. In both cases, the characters are logged and sent to the process. But if the user presses return, a timestamp is also sent to the log.[41]

Also notice that the from the user is matched but not sent to the log. This lets the log look a little cleaner in most editors since the log does not have sequences at the end of each line. Instead, only newlines remain.

If I took a photo of the screen after I finished running it, it might look like this:

56% timeallcmds
1% date
Thu Dec 23 22:57:44 EST 1993
2% echo foo
foo
3% qwertyuiop
command not found: qwertyuiop
3% vi /tmp/foo.c
4%
57%

The transcript file ends up with the following:

date
Thu Dec 23 22:57:44 EST 1993
echo foo
Thu Dec 23 22:57:45 EST 1993
qwertyuiop
Thu Dec 23 22:57:47 EST 1993
vi foo.c^?^?^?^?^?/tmp/foo.c
Thu Dec 23 23:06:31 EST 1993
ihello there
Thu Dec 23 23:06:37 EST 1993
this is line 2
Thu Dec 23 23:06:41 EST 1993
^[:wq
Thu Dec 23 23:06:42 1993
^D

After each line of input is the timestamp when I pressed the return key. While it is not apparent from the scenario above, the transcript below shows where I changed my mind and deleted characters. Initially I typed "vi foo.c" but then I changed my mind and replaced the file name with /tmp/foo.c.

After the invocation of vi, you can see where I typed some vi commands. The idea of logging vi may need more care then I have given here. But it is interesting to see that everything has indeed been logged.

The script ended after I typed the vi exit sequence and then a ^D which exited the shell. You can strip any of this out, of course. You can add another line to send the output from the shell to the log as well. There are many other possibilities.

Unfortunately, it is difficult to extend this script in some ways. In particular, it is not possible to wait for a two character sequence simply by changing the to, say, . Here are just the two patterns that are read from the user with the new pattern.

−i $user_spawn_id
    -re "(.*)

" { . . .
    -re ".+" { . . .

With this new addition, the first pattern will never match. Well, it will try to match but humans type so slowly that both patterns will be tested before the second return arrives. Since the second pattern always matches any character, the likelihood of two returns matching the first pattern is almost non-existent. Humans simply cannot type fast enough.

In Chapter 15 (p. 330), I will show how to use the interact command to solve this problem, and I will show how to emulate the UNIX script command with yet more features.

Matching Any Spawn Id Already Listed

Frequently, different spawn ids may be watched for different patterns as well as a common pattern. For example, suppose you are waiting for pattern X from hostA or hostB, and pattern Y from hostC, or pattern Z from any of the three hosts. You might write:

expect {
    −i "$hostA $hostB" X
    −i "$hostC" Y
    −i "$hostA $hostB $hostC" Z
}

The global variable any_spawn_id contains a predefined value that matches any spawn id named in the current expect command. It can be used to simplify the previous command to:

expect {
    −i "$hostA $hostB" X
    −i "$hostC" Y
    −i "$any_spawn_id" Z
}

any_spawn_id can be used in a list as well. Suppose, you also want to watch one other process (hostD) but only for the common pattern Z. It could be done this way:

expect {
    −i "$hostA $hostB" X
    −i "$hostC" Y
    −i "$any_spawn_id $hostD" Z
}

The expect_before And expect_after Commands

One of the most common uses for any_spawn_id is to check for an eof. Even if an eof is not expected, it is a good idea to test for it. That way the script can gracefully shut down even if something unexpected happens.

Unfortunately, adding eof patterns to all expect commands can make for a lot of extra typing. It is possible to create and call a new procedure that automatically tacks on the eof patterns, but Expect provides a more direct solution.

The commands expect_before and expect_after declare patterns that are used automatically by subsequent expect commands. As an example, consider the following commands. Each one explicitly checks for an eof as well as the pattern. If the pattern is found, the next command is executed. If an eof occurs, the fictitious command eofproc is called.

expect {
    "login:" {send "$user
"}
    eof eofproc
}
expect {
    "password:" {send "$password
"}
    eof eofproc
}
expect {
    "$prompt" {send "$command
"}
    eof eofproc
}

Because the "eof eofproc" is the same in each, it is possible to declare it once using expect_after. The following code behaves identically to the earlier code.

expect_after eof eofproc
expect "login:"    {send "$user
"}
expect "password:" {send "$password
"}
expect "$prompt"   {send "$command
"}

As you can see, the code is much shorter than before. You can drastically simplify a lot of code this way and at the same time make it much more robust.

The difference between expect_before and expect_after is the order in which the patterns are applied. Patterns declared using expect_before are tested first—before any patterns in the expect command. Patterns declared with expect_after are tested last—after any patterns in the expect command.

This means that you can use the same patterns in the expect_after or expect_before commands as in the expect command. Only one pattern will be matched. For instance, it probably makes sense to treat the eof in a different way when the spawned process exits normally. Consider the following script:

spawn $program
expect_after {
    eof "$program died unexpectedly?"
    exit 1
}
expect $prompt {send $cmd1}
expect $prompt {send $cmd2}
expect $prompt {send $cmd3}
expect $prompt {send $exit-cmd}
expect eof {puts "program completed normally"}

This script performs several interactions before telling the program to exit. In the first four expect commands, an eof is unexpected but will be matched by the pattern in the expect_after command. In the last expect, the eof is matched by the explicit pattern in the command itself. Because expect_after patterns are matched after expect patterns, the expect_after action will not be executed. Instead, when the program exits normally, the script will print:

program completed normally

Suppose a script needs to know if the operator is about to take down the system. For example it could cleanly wrap up what it is doing rather than having the system die in the middle of its work. An expect_before command could declare a pattern and action to do this:

expect_before "system going down" wrapup

The procedure wrapup would be called if "system going down" is ever seen. Since expect_before is used, the pattern will be checked before any patterns in the expect command.

When an expect_before or expect_after action is triggered, it evaluates as if it originally appeared in the current expect command. For example, variable references in the action are evaluated in the context of the scope of the expect command. And actions such as break and continue affect the loop enclosing the expect.

expect_before and expect_after take the very same arguments as the expect command. For example, multiple patterns can be declared. Even the −i flag can be used. The chess script shown earlier can benefit from the following expect_before command, which terminates the script if either chess program resigns.

expect_before {
    −i $any_spawn_id eof {
        send_user "player resigned!
"
        exit
    }
}

As I mentioned on page 254, the spawn id any_spawn_id matches any spawn id used in the expect command. This works as well with expect_before and expect_after. Similarly, any any_spawn_id matches all spawn ids used in any expect_before or expect_after commands whether any_spawn_id is used from expect, expect_before, or expect_after.

As before, if an action needs to know from which spawn id the pattern was matched, it can check expect_out(spawn_id).

As the previous example shows, being able to use any_spawn_id from expect_before is very useful.[42] It avoids the burden of having to change the arguments of expect_before each time the spawn ids in the expect command change. If you need to call expect_before before every expect, then there is no benefit to using expect_before.

When using this technique, expect_before relies on expect for the spawn ids. In all the examples so far, the −i flag and spawn id have preceded a pattern. A pattern is not necessary, however. The mere use of the −i flag alone associates the following spawn id with any_spawn_id.

Using the chess script, you can wait for an eof from both players while only waiting for a pattern from one. This is done using the same expect_before command as above. But in the expect command itself, the first −i flag has no pattern.

expect_before {
    −i $any_spawn_id eof {
        puts "player resigned!"
        exit
    }
}
expect {
    −i $id1 −i $id2 -re ".. (.*)
" {
        send −i $id1 $expect_out(1,string)
    }
}

Contrast the expect command to the following:

expect {
    −i "$id1 $id2" -re ".. (.*)
" {
        send −i $id1 $expect_out(1,string)
    }
}

In the first version, the pattern is only expected from spawn id id2. In the second version, the pattern is expected from either spawn id.

How Long Are expect_before And expect_after In Effect?

All the examples so far have used a single expect_before in the script. It is possible to use multiple expect_before commands. The effect of multiple expect_before commands either augment or modify the effect of previous expect_before commands.

By default, the patterns named by expect_before remain in effect until another expect_before. (Similarly with expect_after.) Consider the following commands:

expect_before "bpat1" bact1
expect "p1"
expect "p2"
expect_before "bpat2" bact2 "bpat3" bact3
expect "p3"

When expect is waiting for p1, it also waits for bpat1 (from the expect_before). At the next command, expect waits for p2 and bpat1. However the last expect command waits for p3, bpat2, and bpat3. The bpat1 pattern is no longer waited for. The effect of the second expect_before is to replace the patterns and actions of the first expect_before.

As long as you are working with the same spawn id, an expect_before replaces the patterns and actions of the previous expect_before.

If you change spawn ids either by using the spawn_id variable or by using an explicit −i flag, the new expect_before does not affect the previous expect_before. Consider the following:

expect_before −i $proc1 "bpat1"
expect_before −i $proc2 "bpat2"

After execution of these two commands, subsequent expect commands wait for both bpat1 from proc1 and bpat2 from proc2. This behavior is convenient. As you spawn new processes, old ones continue to be watched and are unaffected by new processes being created.

Here is a more complex example:

spawn program; set proc1 $spawn_id
spawn program; set proc2 $spawn_id
spawn program; set proc3 $spawn_id
spawn program; set proc4 $spawn_id
spawn program; set proc5 $spawn_id

expect_before −i $proc1 "bpat1"
expect
expect_before −i "$proc1 $proc2" "bpat2"
expect
expect_before −i "$proc2 $proc3" "bpat3" act3 "bpat4"
expect
set spawn_id $proc1
expect
expect_before "bpat5"
expect

After the first expect_before, the expect command waits for bpat1 from proc1. It also returns if an eof is received from either the currently spawned process (proc5) or proc1.

The second expect command waits for the pattern bpat2 from either proc1 or proc2 and an eof from either of them or the currently spawned process. The pattern bpat1 is forgotten because proc1 was specified with a different pattern in the second expect_before command.

The third expect command waits for the patterns bpat3 and bpat4 from proc2 and proc3. proc1 is still monitored for bpat2. All of these processes including the currently spawned process are monitored for an eof.

The next expect comes immediately after spawn_id is changed. This expect command waits for the patterns bpat3 and bpat4 from proc2 and proc3, bpat2 from proc1, and an eof from proc1, proc2, and proc3. proc4 and proc5 are not checked for anything.

The final expect_before changes the patterns associated with the current process which is now proc1. So the final expect command waits for the patterns bpat3 and bpat4 from proc2 and proc3, bpat5 from proc1, and an eof from proc1, proc2, and proc3. As before, proc4 and proc5 are not checked for anything.

The patterns remembered by expect_before are completely separate from those of expect_after. In the following fragment, the expect command looks for both pattern X and pattern Y from the currently spawned process.

expect_before "X"
expect_after "Y"
expect

Using expect_before And expect_after With The Currently Spawned Process—DANGER

When an expect_before command is used with patterns that have no explicit spawn id, the patterns are associated with the currently spawned process at the time of the expect_before command rather than at the time of the expect command.

Consider the following:

spawn proc1
expect_before "pat1" action1
spawn proc2
expect pat2 action2

The expect_before associates the pattern pat1 with proc1. Later, the expect command will wait for pat2 from the spawned process proc2. The pattern pat1 will be ignored if it comes from proc2. Action action1 will only be executed if pat1 is seen coming from proc1.

This behavior is consistent, but nonetheless, may seem a little non-intuitive in certain contexts. Consider the following:

expect_before "pat1" action1
spawn proc2
expect "pat2" action2

This script is the same as before, except that no process has been spawned before the expect_before. Thus, pat1 will be expected from the standard input since that is the default “process” at that point.

It is a common error to use expect_before before the appropriate spawn id has been defined. The solution is either to use an explicit −i flag or to use expect_before after the spawn id has been correctly set. In this example, reversing the order of the first and second lines suffices.

spawn proc2
expect_before "pat1" action1
expect "pat2" action2

Undoing The Effects Of expect_before And expect_after

If a spawn id is closed, the patterns from expect_before and expect_after associated with the spawn id are removed. It is also possible to explicitly remove the patterns associated with a particular spawn id. This is done by issuing the expect_before command with the spawn id but no patterns. For example, to remove the patterns associated with proc:

expect_before −i $proc

This form of expect_before supports all the same syntax as the other forms. For example, multiple spawn ids can be given either separately or together:

expect_before −i $proc1 −i $proc2
expect_before −i "$proc1 $proc2"

New patterns and spawn ids can be named at the same time. The following command removes any “before” patterns associated with proc1 and associates the pattern pat with proc2.

expect_before −i $proc1 −i $proc2 "pat" action

A spawn id with no patterns is one place where the meaning between expect and expect_before differ. Consider the following two commands:

expect
expect_before

The expect command waits for an eof from the currently spawned process (or it times out). The expect_before command merely removes any patterns for the current spawn id established by a previous expect_before. In order to establish the eof pattern, the expect_before command must explicitly mention it.

expect_before eof

Information On The Current expect_before And expect_after Patterns

Both expect_before and expect_after can be queried for the patterns with which they are currently associated. Use "-info" as the first argument to expect_before or expect_after.

expect_before -info

By default, information about the current spawn id is returned. Here is a simple example of using expect_before and then verifying it.

expect1.1> expect_before {
+> pat act
+> eof eofact
+> -re repat
+> }
expect1.2> expect_before -info
-gl pat1 act1 eof eofact -re repat {}

The output of "expect_before -info" may not immediately look recognizable, but it is functionally identical to the original specification. The "-gl" signifies that the first pattern is a glob pattern. A pair of braces signifies that there is no action associated with the last pattern.

The output of "expect_before -info" may be used as an argument to expect_before again. This could be useful if you want to temporarily change expect_before and then reset it. To do this, first query and save the settings in a variable. Later, reset them by calling expect_before with the old settings.

While "expect_before -info" looks (and indeed is) a list, it is also a string. To prevent expect_before from treating the entire string as a simple pattern, use the -brace argument (or the eval command). It looks like this:

set oldpats [expect_before -info]
# modify expect_before patterns here
. . .
# now reset them
expect_before -brace $oldpats

With an optional spawn id, information about the named spawn id is returned.

expect_before -info −i $proc

Only one spawn id can be explicitly named at a time. However, the flag -all may be used to get the information on all the spawn ids simultaneously. In this case, −i flags are also produced in the output.

expect1.3> expect_before -info -all
−i 0 -gl pat1 act1 eof eofact -re repat {}

Notice that the −i flag is a zero—the actual value of the current spawn id. The "-info" flag always returns the real values of spawn ids. It has to, even if the original spawn id was provided using an expression such as $proc because the original expect_before never saw the string "$proc“. Tcl replaces it with the value before expect_before ever gets a hold of the arguments. For the same reason, if you specify a pattern by using brackets or the form $variable, the patterns will be returned in their literal form. Any special characters are appropriately quoted so that they can be reused as arguments without damage. However, the representation may not match your original form, again for the same reason—expect_before will not see the arguments until they have been massaged by Tcl.

Here is an example:

expect1.1> expect_before "\*"
expect1.2> expect_before -info
-gl {*} {}

Although I entered ""\*"“, "expect_before -info" returned "{*}“. However, they are functionally identical.

expect_before And expect_after Actions

Actions from the expect_before command are executed using the scope of the current expect command. Consider the fragment below. If an X is found in the output of the currently spawned process, the "puts $a" action is executed. The value of a is found from the scope of the exp procedure. Hence local is printed.

proc exp {} {
    set a "local"
    expect
}
set a "global"
expect_before X {puts $a}
exp

Similarly, control commands such as break, continue, and procedure calls also execute in the context of the current expect command. For example, if an exp_continue action is executed due to a pattern from an expect_before, the current expect is restarted.

Indirect Spawn Ids

Earlier, I showed how to store a list of spawn ids in a variable and pass that to the expect command. In this case, the argument to the −i flag is called a direct spawn id.

set list "$spawn_id1 $spawn_id2 $spawn_id3"
expect −i $list pattern {
    command
    exp_continue
}

Instead of passing a list directly, it is occasionally useful to pass the name of a global variable that contains the list of direct spawn ids. This is known as an indirect spawn id. For example:

set list "$spawn_id1 $spawn_id2 $spawn_id3"
expect −i list pattern {                                        ;# DIFFERENT!  No
"$"!!
    command
    exp_continue
}

This example is identical to the previous example except that the list variable following the −i flag is passed without the $ preceding it. When the expect command begins, it reads the variable and uses the spawn ids in the list as if they had been specified directly. If the variable is changed while the expect command is in progress, the expect command accordingly modifies what processes are watched for patterns.

The following example shows how new spawn ids could be added to an expect in progress. Each time the add command is read, its argument is appended to the spawn id list stored in list.

expect −i list "add (.*)
" {
    lappend list expect_out(1,string)
    exp_continue
}

Indirect spawn ids can also be used with expect_before and expect_after. For example, the following command removes closed spawn ids from the list, terminating the expect command when the list is empty and continuing it otherwise. It is possible to know which spawn id to remove from the list by examining the expect_out(spawn_id) variable.

expect_before −i list eof {
    set index [lsearch $list $expect_out(spawn_id)]
    set list [lreplace $list $index $index]
    if [llength $list] exp_continue
}

The first command in the action locates where the spawn id is in the list. The next command removes the spawn id. After the exp_continue, the list is reread and the expect command continues.

The previous example explicitly tested the list to see if it was empty. In the context of that example, it does not make sense to have an empty list. The expect command would not have any process to listen to.

There is no restriction in expect itself that requires lists to be non-empty. Indeed, it is not even necessary for the variable containing the list to exist. expect ignores patterns for which it has no spawn ids. If the expect command has no valid spawn ids at all, it will just wait. In Chapter 14 (p. 303), you will see that asynchronous events can provide a way of coming out of a spawn id-less expect.

As with direct spawn ids, the -info and −i flags can be used to retrieve information on indirect spawn ids. For example, the following command retrieves information about the patterns associated with the indirect spawn id list ping:

expect_before -info −i ping

If you retrieve the patterns using a direct spawn id, expect_before returns all the patterns associated with the spawn id, irrespective of whether the patterns were originally specified by the direct or indirect form of the spawn id. The patterns associated with the indirect form can be suppressed by using the -noindirect flag.

expect_before -info −i $id -noindirect

There is no -direct flag since the patterns associated only with the direct form are returned by using −i with the indirect spawn id.

Indirect Spawn Ids—Are They Really That Useful?

Indirect spawn ids are most useful with long-lived expect commands. They are useful if you are looping inside of expect with exp_continue. But they are even more useful with expect_before and expect_after. You can avoid reissuing these commands every time a process is created or dies by using indirect spawn ids.

This idea can be simulated by writing a procedure that calls, say, expect_before for you each time a process is created. So the indirect spawn ids can still be considered a mere convenience at this point.

Later in the book, you will learn about the expect_background command (see Chapter 19 (p. 425)) which runs in the background and the interact command (see Chapter 16 (p. 355), both of which are generally very long-lived. Indirect spawn ids are particularly useful with these commands.

Exercises

  1. Write an ftp mirror script—which copies a file hierarchy from one anonymous ftp site to another. Do it using a single spawn id. Do it again, but use two spawned ftp processes—one to retrieve the listings and one to do the I/O. What are the advantages and disadvantages of these two approaches.

  2. Extend one of the scripts from the previous exercise to update multiple ftp sites simultaneously.

  3. Write a script that watches a user interacting with a process and produces a new Expect script that automates the session.



[41] In Chapter 23 (p. 522), I will show a much more efficient and flexible way of generating timestamps.

[42] Unless I am talking about the order in which patterns are matched or I explicitly mention an exception, everything I say about expect_before also holds true for expect_after.

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

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