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.
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
.
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.
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 telnet
s 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.
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) } } }
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.
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.
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!
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.
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.
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 }
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.
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
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.
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
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
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.
expect1.1>expect_before "\*"
expect1.2>expect_before -info
-gl {*} {}
Although I entered ""\*"
“, "expect_before -info
" returned "{*}
“. However, they are functionally identical.
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.
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 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.
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.
Extend one of the scripts from the previous exercise to update multiple ftp
sites simultaneously.
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.
3.138.122.4