In earlier chapters, interact
was used in a very simple way. In reality, the interact
command simplifies many tasks and opens up a world of new problems that can be solved. In this chapter, I will describe the more common uses for interact
. In the next chapter, I will focus on using interact
with multiple processes.
In Chapter 3 (p. 82), I introduced the interact
command in the context of a script to automate ftp
. The script carried out the initial part of the procedure—entering the user name and password—and then returned control to the user by calling interact
.
The interact
command is much more flexible than that example demonstrated. interact
can also:
execute actions when patterns are seen from either a user or a spawned process
allow the user to take control of a spawned process, and return control to the script for further automated interaction, any number of times
suppress parts or all of an interaction
connect two or more spawned processes together, pairwise or in other combinations
Many of the things interact
does can also be done by expect
, but interact
can do them more easily and efficiently. In this sense, interact
is a higher-level command than expect
. In other ways, expect
and interact
are duals. They do the same thing but have a very different way of looking at the world. As I explain interact
, I will frequently bring up expect
to compare the similarities and contrast the differences between the two.
In its simplest form, the interact
command sets up a connection between the user and the currently spawned process. The user’s terminal is put into raw mode so that the connection is transparent. It really feels like the user is typing to the process.
If the spawned process exits, the interact
command returns and the next line of the script is executed. In a simple script such as the anonymous ftp
script (aftp
), the interact
command is the last line in the script, so the script simply exits when interact
does.
Like the expect
command, the interact
command can execute actions upon detecting patterns. However, interact
and expect
behave very differently in many respects.
The syntax for specifying patterns and actions is similar to expect
. Patterns and actions are listed as pairs of arguments. For example, the following interact
command causes the date to be printed if ~d
is typed by the user.
interact "~d" {puts [exec date]}
By default, a matched pattern is not sent on to the process. (Later, I will show how to change this behavior.) Thus, in this example, the process never sees the ~d
.
Unlike the expect
command, interact
continues after matching a pattern and executing an action. interact
continues shuttling characters back and forth between the user and process. It also continues matching patterns. In the example above, the ~d
pattern can be matched again and again. Each time, the action will execute.
As with the expect
command, additional pattern-action pairs may be listed. Also, all the arguments may be surrounded by a pair of braces, provided they do not all appear on the same line as the command. The -brace
convention from the expect
command is also supported. Here are two ways of expressing the same command:
interact "~d" {exec date} "~e" {eproc} interact { "~d" {exec date} "~e" {eproc} }
There are all sorts of useful effects that can be accomplished with these patterns. A very simple one is translating characters. For example, one of the problems with the UNIX terminal driver is that you can have only one erase character. Yet, it is often convenient to have both the backspace key and and the delete key generate erase characters—especially if you frequently switch from using one badly-designed keyboard to another. Assuming that you have defined delete as the erase character already (using stty
), the following script translates the backspace character to a delete character. This effectively provides you with two erase characters.
interact "" {send "177"}
This technique is often useful when you have to connect (e.g., telnet
) to another computer that does not support remapping of keys at all. If you are used to pressing delete but the remote system wants to see backspace, just reverse the above script:
interact "177" {send ""}
Single character translations are not the only possibility. For example, the following interact
command maps all the lowercase keys to their Dvorak equivalents. There is no algorithmic mapping so each translation is explicitly spelled out.
interact { "q" {send "`"} "w" {send ","} "e" {send "."} "r" {send "p"} "t" {send "y"} "y" {send "f"} "u" {send "g"} "i" {send "c"} "o" {send "r"} "p" {send "l"} "s" {send "o"} "d" {send "e"} "f" {send "u"} "g" {send "i"} "h" {send "d"} "j" {send "h"} "k" {send "t"} "l" {send "n"} ";" {send "s"} "`" {send "-"} "z" {send ";"} "x" {send "q"} "c" {send "j"} "v" {send "k"} "b" {send "x"} "n" {send "b"} "," {send "w"} "." {send "v"} "/" {send "z"} }
Patterns can differ in length. For example, you can make abbreviations that automatically expand to their longer forms. Imagine that you are a physiologist writing a report on “glomerular nephritis”. Rather than typing this phrase literally each time, you can define a few keystrokes which will expand into the full phrase.
interact { "~gn" {send "glomerular nephritis"} "~scal" {send "supercalifragalisticexpealidocious"} "~adm" {send "antidisestablishmentarianism"} "relief" {send "rolaids"} }
The effect of this is very similar to that offered by editors such as vi
’s autoabbreviation mode. However, unlike these programs, interact
’s abbreviations work inside of any program that is running underneath Expect. If you spawn a shell and then interact with it, any program started from that shell is able to use these abbreviations.
Incidentally, I often start interact
patterns with some unusual character such as a tilde (~
). It is a good idea to use a special character like this. It protects you from unthinkingly entering a sequence for which interact
is watching. Characters or sequences like these are knowns as escapes. Unfortunately, there is no best escape character or sequence. Tilde, for example, is often used in specifying home directories. If ~gn
was someone’s home directory, for instance, I would not be able to type it using the interact
command above. It would always be intercepted and translated to glomerular nephritis
.
One solution is to “double up” the escape to generate the same literal escape. Adding the following line to the example above guarantees that you can always be able to send a tilde followed by anything else by just typing an extra tilde first.
"~~" {send "~"}
With this addition, you can send ~gn
by entering ~~gn
even though ~gn
is a pattern itself.
If you cannot give up a single character, sometimes two or even more characters work better. It is also possible to mix escapes. For example, some can start with one tilde, others with two tildes. And it is not necessary that all escapes be the same. For instance, some can start with %
or ~
, and yet others can have none at all as in the Dvorak script.
Actually, interact
does not know anything about escapes. To it, characters are just characters. While I have been using the term “patterns”, interact
’s default patterns have no wildcard matching at all and work in the style of the exact strings supported by the -ex
flag in the expect
command. For example, the following command runs starproc
only when an asterisk is typed. No other character matches the "*
“.
interact "*" starproc
Backslash is still interpreted directly by Tcl and therefore retains its special properties. For example,
matches a return, matches a backspace, and
followed by one to three octal digits matches a character specified by its octal value. A backslash itself can be matched by two backslashes or enclosing it in braces. Here are some examples:
interact "\" proc ;# match backslash interact " " proc ;# match return interact "\r" proc ;# match backslash and r character interact "\ " proc ;# match backslash and return
With these interact
patterns, there is no feedback analogous to expect_out
to record what matched. The reason is that you can always tell what matched. The characters only match themselves, so the patterns automatically define the resulting match.
Much like the expect
command, the interact
command also supports regular expressions. These do provide feedback to the application; however, regular expressions are generally overkill for reading user input. More importantly, matching complicated user input in raw mode is very hard; users expect to be able to edit their input but interact
simply passes the editing keystrokes on to the pattern matcher. For this reason, it usually makes more sense to detect these situations before they happen—by disabling raw mode temporarily and using expect_user
. I will provide examples of this later on.
This style of matching that I have used so far is called exact matching. It is simple to use, but you must take precaution that your patterns do not look like any of interact
’s flags. Rather than memorizing them, the simplest way to prevent this from happening is to use the -ex
flag. This is especially useful when patterns are stored in a variable.
interact { -ex $var action1 -ex "-ex" action2 }
The first pattern above matches whatever is stored in the variable var
. The second pattern matches the string -ex
itself. Without the -ex
flag, interact
would look for action2
.
Most of the time, you will not need to use -ex
and you should not feel obliged to use it. The -ex
flag is only necessary to match patterns that would otherwise be accepted as a keyword (e.g., eof
, timeout
) or a flag (any argument starting with a hyphen).
Output from the spawned process can be matched by using the -o
flag. (Think of the “o” as standing for the “opposite” or “other direction”.) The following command translates unix
into eunuchs
, vms
into vmess
, and dos
into dog
.
interact { -o "unix" {send_user "eunuchs"} "vms" {send_user "vmess"} "dos" {send_user "dog"} }
All the patterns before the -o
apply to the user keystrokes. All the patterns after the -o
apply to the spawned process. For example, the following command is similar to the previous one except that if the user accidentally types one of the humorous nicknames, it is translated back to the correct name. Notice that the first three actions use send
to send to the spawned process. The latter three actions use send_user
to send to the user.
interact { "eunuchs {send "unix"} "vmess {send "vms"} "dog {send "dos"} -o "unix" {send_user "eunuchs"} "vms" {send_user "vmess"} "dos" {send_user "dog"} }
This example is artificial and may not seem to make a convincing case for using -o
. In practice, matching output from a spawned process almost always requires using regular expressions, in part because process output can be very verbose.
Earlier I said that regular expressions are hard to use from interact
. That is only true when reading keystrokes from users. Spawned processes, on the other hand, never do editing so the associated complexities disappear. Regular expressions are an extremely convenient way of matching output from spawned processes. I will describe regular expressions in the next section.
The interact
command provides the ability to match regular expressions just like the expect
command. The syntax is identical. The pattern is preceded by the -re
flag. For example, the following sends an X
each time the user presses a tilde followed by any other character.
interact -re "~." {send "X"}
The tilde matches a tilde while the period matches any other character. Other special characters work as before.
When using regular expressions, it is possible to find out what characters were typed. Similar to the expect
command, the interact
command writes its matches to the array interact_out
. If the characters ~abc
were entered in the previous example, interact_out(0,string)
would be set to ~a
. The bc
would be sent on to the spawned process and would not be saved anywhere.
As before, the -indices
flag causes indices to be saved as well. Using this flag, the complete set of assignments would be:
set interact_out(0,start) "0" set interact_out(0,end) "1" set interact_out(0,string) "~a"
The number 0 plays the same role that it did in the expect
command. Parentheses can be used to pick out submatches. Matching strings are stored in interact_out
using the indices 1 through 9.
There is no buffer
element in interact_out
as there is in expect_out. The reason for this is that the interact
command processes unmatched characters as they arrive. So by the time a match occurs, unmatched characters are no longer present. Hence, matches always start with the first character remaining in the output buffer. I will revisit this concept shortly (page 331). For now, just remember that the buffer
element of interact_out
is never written. In theory, it would always have the same value as the "0,string
" element so the buffer
element is redundant and omitted for efficiency.
A simple use for interact_out
is shown in the next script, written to aid a person who was unable to press two keys at the same time. Uppercase letters were not a problem—the user could press the shift-lock key, then the letter, then the shift-lock again to go back to lower case. But control characters could not be entered in a similar fashion because there was no way to lock the control key down.
I used a two character escape—after entering a /
and , the next character would be turned into its control equivalent. This worked out well. I did not want to reserve a single character as an escape mechanism—all the single characters might have to be entered, and it was very intuitive to remember since the sequence
/X
graphically looks like ^X, a common way of writing “control-X” in text.
spawn $env(SHELL) interact -re "/\\(.)" { ;# match /char scan $interact_out(1,string) %c i send [format %c [expr $i-96]] }
The first command spawns the user’s requested shell. The second command performs the interaction with the appropriate regular expression. The pattern /\\(.)
matches the three character sequences beginning with /
and . The four backslashes are required to match a literal backslash because inside double-quotes, Tcl represents a backslash by two backslashes and the regular-expression pattern matcher does the same thing. (See Chapter 4 (p. 91) for a complete explanation of this.)
The remaining commands are executed if there is a match. The variable interact_out(1,string)
contains the character itself. Tcl’s scan
command converts the character to its integer equivalent in ASCII. Subtracting 96 converts a lowercase value to a control value, and then format
converts the integer value back to an ASCII character. Finally, the send
command sends it on to the spawned process.
This all looks complicated but it works nicely and executes quickly. The script was a very short solution for the problem. This technique can be applied to many other problems. As an exercise, rewrite the Dvorak script (shown earlier) so that it uses a single action instead of one for each possible character.
As I mentioned earlier, regular expressions are used more often to match output from a spawned process than from a user. A simple but common use is to detect and deal with system inactivity monitors. For example, a VMS system used by a colleague of mine logs users out after 15 minutes of inactivity in the top-level shell (called “DCL”). DCL starts warning after 10 minutes of inactivity. The warnings look like this:
%DCL-W-INACT, session inactive for more than 10 minutes - you will be logged out in 5 minutes.
There is no way of disabling this behavior or message from VMS. To avoid the messages, they use the following interact
command:
interact -o -re "%DCL-W-INACT.* " { send "show time " }
The regular expression matches the warning message whenever it appears. The matched characters themselves are discarded. The .*
near the end allows it to absorb all of the error message without specifying the entire text. Next, a very simple command (show time
) is sent which resets the inactivity timer.
The result is that users see timestamps every 10 minutes when they are idle. But they see no irritating messages, and more importantly, they are not logged out automatically.
With one additional expect
command in the action (expect -re ".*$prompt"
), the timestamp and following prompt could be absorbed as well. This might be useful if you are trying to build a seamless application for users who should not have to know what is going on behind the scenes.
The talk
program sends messages to the console window describing the user requesting the connection. A typical message looks like this:
Message from Talk_Daemon@lobotomy at 0:55 ... talk: connection requested by bimmler@research. talk: respond with: talk bimmler@research
This message can be captured using the -console
flag described in Chapter 13 (p. 300). The following script uses this flag and the resulting message to automatically start an xterm
that runs a talk
session back to the original user.
spawn -console $env(SHELL) interact -o -re "talk: respond with: talk ([^ ]*)[ ]" { exec xterm -e talk $interact_out(1,string) & }
The pattern is not quite trivial. What is not apparent when looking at the message is that talk
may pad the username with spaces. Hence, the pattern has to go to some effort to allow the username to terminate at either a space or return character. Can you think of a better pattern for this?
As characters are entered, they are compared to the patterns. Characters that do not match any of the patterns are sent to the spawned process. This includes characters before and after any matches. Any characters involved in a match are not sent to the spawned process. Consider the following interact
command, which sends "hugs and kisses
" if it sees XOX
.
interact "XOX" {send "hugs and kisses"}
If the user types AXOXB
, the spawned program receives "Ahugs and kissesB
“. The A
does not match so it is sent literally. The XOX
is replaced by the phrase "hugs and kisses
“. And the trailing B
does not match so it is sent literally.
If the user types AXOYB
, the spawned program receives AXOYB
. The pattern XOX
cannot match any part of what was entered, so it is all sent on.
If the user types XOXO
, the spawned program receives "hugs and kissesO
“. The first XOX
matches so it is translated and sent on, but the O
that remains cannot possibly match XOX
so it is sent literally.
If the user types XOXOX
, the spawned program receives "hugs and kissesO
" following the logic of the previous case. The trailing X
can match the pattern XOX
if another OX
is entered so the interact
command waits for more characters to be entered before deciding whether to send on the X
or not. Suppose the user instead enters Z
. In this case, the X
can no longer match so interact
sends the string XZ
to the spawned process. What would have happened if the user entered another X
rather than a Z
? The new X
would be eligible to match but the previous could no longer match so the previous X
would be sent to the spawned process. The user will have typed XOXOXX
and the spawned process will have received "hugs and kissesOX
“. The last X
remains with interact
waiting for more characters.
To summarize, the interact
command buffers input until it can decide whether characters can or cannot match. If they cannot match, they are removed from the buffer and sent to the spawned process. If they match, their associated action is executed and the characters are removed from the buffer. And if they might match if more characters arrive, nothing at all happens—the characters simply remain buffered. This buffering behavior allows interact
to do “the right thing” no matter how slowly or quickly characters are entered.
If two or more patterns can match the same output, only one action will be executed. The action corresponds to the pattern that appears first in the command. This is just the way the expect
command works. Here is an example that has both an exact pattern and a regular expression. When abc
is entered, action1
is executed while action2
is executed if axc
is entered. If the patterns were reversed, the exact pattern would never match since the regular expression would always match first.
interact { "abc" action1 -re "a.c" action2 }
Pattern matching works differently in one respect between expect
and interact
. The expect
command attempts to match a pattern beginning at every place in the input. Only after failing to match anywhere will expect
try another pattern. In contrast, if interact
fails to match a pattern starting from a particular place in the input, it then tries the next pattern beginning at the same place in the input.
The difference is only noticeable in a situation where two or more patterns can match in the input. This does not normally occur in user input but can be common when making substitutions in spawned process output, such as when using -o
. Earlier I showed an example of -o
. Here it is again:
interact { -o "unix" {send_user "eunuchs"} "vms" {send_user "vmess"} "dos" {send_user "dog"} }
Now consider what happens when a spawned process produces the following output:
This software runs on both vms and unix.
Both unix
and vms
appear in the output so both can potentially match. Although unix
is the first pattern listed, it appears later in the output so it does not match at first. Instead, vms
matches and its action is executed. The input from "This software
" up to "vms
" is removed from the output and interact
continues running. It then matches unix
and the corresponding action is executed. The resulting output looks like this:
This software runs on both vmess and eunuchs.
With the expect
matching algorithm, vms
would not have been matched. Reversing the order of the patterns would change this behavior—allowing expect
to match vms
. But then, expect
would misbehave if the order of vms
and unix
were reversed in the output.
There is no way to make expect
work precisely as interact
does and vice versa—which is partly why there are two different commands. Generally though, expect
and interact
are used for very different types of interaction, and their algorithms are well suited to their purpose. The expect
command is primarily used to look for responses or prompts where it is important not to be distracted by things in the middle that may otherwise resemble patterns. In contrast, the interact
command is not normally used for recognizing prompts but instead makes substitutions wherever they appear.
Normally, the interact
command depends on the spawned process to echo characters. For example, when you have spawned a shell and are interacting with it through the interact
command, all the printable characters that you type are echoed by the shell. Nonprintable characters are not usually echoed back but instead invoke special actions such as erasing previous characters or generating signals. As with echoing, this special processing of nonprintables is also handled by the shell. The interact
command does no special processing. It just shuttles characters back and forth.
The interact
command works the same way when matching patterns. That is, no special action is taken to echo characters. However, because the characters are being buffered and not sent on to the spawned process, you will not see them appear on your screen. This is similar to the way the tip
program works. tip
does not echo its escape character unless additional characters are entered that force the escape not to match any known sequence.
For patterns that match short character sequences, the lack of echoing is rarely a problem. In most cases, users do not need to see a single character being echoed before they enter the next one. But patterns that are long enough (however you choose to define this) can use some sort of feedback. A simple strategy is to echo the typed characters.
By preceeding a pattern with the -echo
flag, interact
echoes characters that match a pattern. Partial matches are also echoed. This echoing is most useful with actions that cause a local effect, such as modifying the behavior of a script. In this case, the user might not otherwise get any feedback.
The following script behaves similarly to the UNIX script
command, which records all input and output in a file called typescript
. However, this script also allows the user to turn recording on and off by typing ~r
(to record) or ~s
(to stop recording). This can be used to avoid recording things such as vi
or emacs
editor interactions in the middle of a longer shell session. The script starts with recording disabled.[54]
spawn $env(SHELL) catch {exec rm typescript} interact { "~s" {log_file} "~r" {log_file typescript} }
As is, the script gives no feedback when the user types ~s
or ~r
, nor when the actions are executed. Adding -echo
allows the escapes to echo.
interact { -echo "~s" {log_file} -echo "~r" {log_file typescript} }
Once patterns are completely matched, you can add any kind of additional feedback you like. For example:
interact { -echo "~s" { send_user " stopped recording " log_file } -echo "~r" { send_user " recording " log_file typescript } }
The
at the beginning of each send_user
puts the message on another line, but you can take advantage of what is already on the screen to accomplish interesting effects. For example, you can incorporate the echoed keystrokes into new messages by writing the send_user
commands as follows:
send_user "topped recording " ;# prefaced by "s" send_user "ecording " ;# prefaced by "r"
This kind of thing can be very cute but generally leads to torturous code, so I only recommend it in very simple cases.
A problem with -echo
is that a user may start entering a pattern that echoes but then go on to enter other characters that do not match the remainder of the pattern. The buffered characters plus the new ones will be sent to the spawned process. Assuming that the spawned process echoes them, if a pattern using -echo
was causing them to echo already, the user will see characters echoed twice.
For example, in the recording script above, suppose the user enters ~q
. The tilde matches and is echoed by Expect. The q
does not match, so the tilde and q
are sent to the spawned process and are likely echoed by the spawned process. Unfortunately, there is no trivial way to “unecho” a character without getting involved in knowing how to erase characters on the screen as well as what was on the screen in the first place. So the user ends up seeing "~~q
“—one tilde too many.
There is no perfect solution for this problem except to avoid it to begin with (perhaps by not using -echo
at all). But the next best possibility is to choose patterns that are less likely to match at any point unless the user is definitely entering a pattern.
In practice I rarely use -echo
. If the user really needs feedback, I give it immediately after recognizing that a pattern is being entered. There is no special support for this. Instead, simply use the escape prefix itself as the sole pattern and then pass control to another procedure that carries out the rest of the recognition. Here is an example from a script which uses ~~
to start commands.
interact "~~" cmd
In the cmd
procedure, the user can now be directly prompted for commands:
proc cmd {} {
send_user "command (g, p, ? for more): "
expect_user {
"g" get_cmd
"p" put_cmd
"?" help_cmd
"~" {send "~~"}
. . . and so on
}
}
The user now clearly knows what they are dealing with because the prompting is very explicit. There is no way for the user to be confused about echoing because the two-tilde sequence always invokes the cmd
procedure, which in turn entirely suspends the interact
until the procedure returns.
Note that if the user really intended to enter two tildes and not have them interpreted as a command, another tilde has to be entered. This is a fairly standard and customary inconvenience with interactions that suspend interact
commands. This style is identical to that used by the UNIX telnet
command when its escape is entered.
In the examples so far, the escape sequences were layered on top of the spawned process. In each case, the user must be informed of these new commands in order to take advantage of them. Indeed, the more obvious the underlying spawned process is, the more likely the user is to forget that there are extra commands available.
Printing out documentation while the program is running can be very helpful and is easy to do. The previous script showed one such example—printing out a list of choices when an escape prefix is entered. Here are some others.
The following extract is from a script that layers several commands on top of the traditional ftp
client to enable it to perform recursive ftp
. (Called rftp
, this script comes with Expect as an example.) These new commands are entered as ~g
(get recursively), ~p
(put recursively), and ~l
(list recursively).
puts "Once logged in, cd to the directory to be transferred and press: " puts "~p to put the current directory from the local to the remote host " puts "~g to get the current directory from the remote host to the local host " puts "~l to list the current directory from the remote host " if {[llength $argv] == 1} { spawn ftp } else { spawn ftp [lindex $argv 1] }
When the script starts up, it prints the messages above, which describe the new commands. Then the script drops into an interact
so that ftp
can enter its usual dialogue. The tilde escape works well, because ftp
does not use tilde for anything anyway.
Below is the complete version of the script to emulate the UNIX script
command. A couple of things have been added to make it more like the original, but most important is the message that tells the user about the new commands. This message prints out immediately when the script starts. The first shell prompt then follows on a new line.
log_file typescript spawn -noecho $env(SHELL) send_user "recording to file typescript " send_user "~s to stop recording, ~r to record " interact "~s" {log_file} "~r" {log_file typescript} send_user "recording to file typescript complete "
Here is how it looks to the user when run:
28% newscript
recording to file typescript
~s to stop recording, ~r to record
1%
By default, characters are not sent to the spawned process until either a match is made or no match is possible. Characters that may potentially match are buffered. It is occasionally useful to disable this buffering.
The buffering is disabled using the -nobuffer
flag. Using this flag, all characters are sent to the spawned process whether or not they match, do not match, or might match in the future.
For example, a site had a modem that was available to all users. The site administrators wanted to monitor the phone numbers being dialed. Using tip
or some other interactive program, there was no way of recording the numbers. They used the following fragment in a script that ran on top of tip
.
proc lognumber {} { interact -nobuffer -re "(.*) " return puts $log "[exec date]: dialed $interact_out(1,string)" } interact -nobuffer " atd" lognumber
The interact
command (see last line) passes all characters to the spawned process. If the user presses return followed by atd
, the lognumber
procedure is invoked. The return forces the command to be entered at the beginning of a line—just one more safeguard against detecting the pattern at the wrong time.
Unlike the example on page 335, lognumber
records everything until another return is pressed. The characters between the first
atd
and the next
are the phone number. Because of the -nobuffer
on the second interact
command, the phone number is sent to the spawned process and echoed normally. The user cannot tell that the number is being recorded.
When the final return is pressed, the pattern matches and the return
action is executed. The return
action simply forces the interact
to return to its caller (more on this in the next section), and the next command, puts
, executes. The puts
records the date and the phone number in a log file.
The log file will eventually look something like this:
Wed Aug 25 21:16:28 EDT 1993: dialed 2021234567 Wed Aug 25 23:00:43 EDT 1993: dialed 3013594830
The phone numbers in the log file will also contain things like backspaces or whatever erase characters the user might press to delete characters and fix errors. You could, of course, write a procedure to interpret these and strip them out after they are matched but before they are put in the log.
interact -nobuffer -re "(.*) " return set number [cleanup $interact_out(1,string)] puts $log "[exec date]: dialed $number
A much easier solution is just to change to cooked mode temporarily and call expect
to read the number. However, this approach also has a drawback in this example—users could conceivably press
atd
in some other context besides dialing a modem. In that case, you may well get a “number” that turns out not to be a number at all. In this example, the likelihood is pretty low—
atd
is a very unusual character sequence. In the general case, more heuristics could be added but there is no sure-fire way to know. For example, the user could be writing a manual on how to dial a modem. Obviously, making this look like a number was being dialed could be crucial to the task!
Like the expect
command, it is possible to have the interact
command cause its caller to break
or continue
. Each of these can be used as an action. For example, in the following loop, if a user presses "+
“, interact
returns and the while
loop breaks.
while {1} { interact "+" break }
The continue
command works similarly. In the following loop, if a user presses "+
“, the interact
returns and the loop breaks. If the "-
" is pressed, the interact
returns, and the while
loop continues.
while {1} { interact "+" break "-" continue }
By default, the return
command does not behave in a way analogous to break
or continue
. The return
command does not cause the caller to return. Instead, when used as an action, return
causes interact
itself to return.
For example, consider the following commands:
interact "X" return puts "interact done"
If the user presses X
, the interact
command returns and the puts
command executes.
This behavior is very useful when you have a problem that is almost automated but has sections that require attention. The script will do most of the work, but you can grab control when necessary. You can interact and then return control to the script.
For example, fsck
, the file system checker, looks for discrepancies in the file system. fsck
describes the problems it encounters and interactively asks whether it should fix the problem. fsck
waits for a y
or n
as an answer. The following script automatically answers one type of question yes
and another type no
. Everything else causes control to be turned over to the user via the interact
command.
for {} 1 {} { expect { eof break "UNREF FILE*CLEAR\?" {send "y "} "BAD INODE*FIX\?" {send "n "} "\? " {interact + return} } }
When the user is through interacting, a plus causes interact
to return, after which the script resumes control and continues answering questions.
In this example, control is completely turned over to the user. This is especially useful when the situation is very complicated and the user might need to explore things a lot. In more controlled situations—for example, when you do not trust the user or when the user does not understand what is happening behind the scene—the script should stick with expect_user
and send_user
commands.
It is also possible to have interact
cause a return
in its caller. This is more like what the expect
command does with a return
action. This behavior is useful if you call interact
from within a procedure. The caller will return if interact
executes the inter_return
command. For example, the procedure below returns if a plus is pressed. If a minus is pressed, the interact
command ends, but the procedure continues executing the next command after interact
.
proc x {} { interact { "+" {inter_return} "-" {return} } send ". . ." expect ". . ."
In Chapter 11 (p. 250), I presented a script that used expect
commands to handle answerback at login. Answerback requires communications to flow in both directions, and this should make you think of interact
. The earlier script required nine lines to solve the problem. Using interact
, it is possible to handle answerback using only one line:
interact -o -nobuffer $prompt return
This command permits the spawned process to interact with the terminal. When the prompt appears, return
is executed which causes interact
itself to return. The -nobuffer
flag allows the prompt to appear in the output even though it has been matched.
This same idea can be used to convert the rlogin
script in Chapter 5 (p. 122) into a telnet
script. Remember that the script provided an interactive rlogin
session with the same directory as the one on the local host. The script assumed that rlogin
does not prompt for either account or password. In comparison, telnet
is guaranteed to prompt. The solution is to substitute the code above in place of the wait for the prompt done by an expect
command.
Here is the resulting script using telnet
:
#!/usr/local/bin/expect -- # telnet-cwd - telnet but with same directory eval spawn telnet $argv if [info exists env(EXPECT_PROMPT)] { set prompt $env(EXPECT_PROMPT) } else { set prompt "(%|#|\$) $" ;# default prompt } interact -o -nobuffer -re $prompt return send "cd [pwd] " interact
The interpreter
command lets you interactively give commands to Expect and is described further in Chapter 9 (p. 225). The interpreter
command is very useful when debugging scripts or avoiding having to hardcode commands beyond some point where the interactions are stable enough. Just call interpreter
with no arguments.
interpreter
The last action in an interact
may be omitted in which case it defaults to interpreter
. The following two commands are equivalent:
interact X interact X interpreter
The first form is simply a shorthand. There is no other inherent value to omitting the action. Note that this shorthand can only be used with the final pattern—with any other pattern, the subsequent pattern would be misinterpreted as the action to the previous pattern. Consider the following two commands. If X
is pressed, the first command invokes interpreter
while the second mistakenly invokes pattern
.
interact X interpreter pattern action interact X pattern action ;# OOPS!
It is common to write scripts that end by leaving the user in an interact
command, interacting with a process in raw mode. The interpreter
command provides a way to get back out to Expect before the script terminates. The environment can then be explored or manipulated while the script is still running.
While I just said that omitting the action is only a shorthand, it is intended to be a compellingly convenient shorthand. Indeed, it should be so compelling that you should never use interact
without some sort of escape unless you have intentionally considered all the implications. When prototyping, get used to writing "interact X
" (or whatever your favorite escape character is) rather than just interact
. It will save you in lots of situations such as when the spawned process hangs.
Normally, a spawned process generates an eof when it exits. This can be detected by using the special pattern eof
. When an eof occurs, the following action is executed. The syntax is identical to expect
. For example, the following command prints out a message when an eof occurs. The return
causes interact
to return to its caller.
interact eof { puts "detected end-of-file" return }
If an eof occurs but no eof
pattern is given, the return
action is executed by default. So you could write the fragment above more simply as just:
interact puts "detected end-of-file"
You usually want to end an action for the eof
pattern with return
. It rarely makes sense to continue the interaction if the spawned process has exited. However, the next chapter has examples where it is meaningful to continue the interaction.
When interact
detects an eof, it automatically closes the connection to the spawned process so that there is no need to call close
explicitly. This is identical to the behavior of expect
when it detects an eof. One tiny difference between expect
and interact
is that interact
can never detect an eof from the user. Because interact
puts the terminal into raw mode, the user has no way of generating an end-of-file. In expect
, a ^D closes the input, but in interact
a ^D is just sent on to the spawned process.
The interact
command can match a null character using the null
pattern. This pattern works the same way as in the expect
command. See Chapter 6 (p. 155) for more information.
The special timeout
pattern matches after a given amount of time. Unlike the expect
command, the timeout
pattern does not depend on the timeout
variable. Instead, the timeout is given after the keyword itself. The timeout is specified in seconds. The action follows the number of seconds. For example, the action in the interact
command below is executed if the user presses no keys for more than an hour.
interact timeout 3600 { send_user "Idle for 1 hour - you are being logged out" return }
In this example, the output of the spawned process is not timed. This is useful in preventing irrelevant system messages (”System going down at midnight.
“) from making the session seem active.
The interact
command uses explicitly specified timeouts so that you can use different timers simultaneously. For instance, the following fragment times out the user after 10 seconds and the spawned process after 600 seconds.
interact { timeout 10 { send_user "Keep typing -- we pay you by the character!" } -o timeout 600 { send_user "It's been ten minutes with no response. I recommend you go to lunch!" } }
On many systems, the following script can be used to fake out shells that automatically exit after periods of inactivity. The script works by detecting the inactivity and sending a space and immediately removing it.
spawn $env(SHELL) interact timeout 3000 {send " 177"}
As shown here, the script waits for a little less than an hour under the assumption that the spawned program’s timeout is exactly an hour. You can modify or parameterize the timeout. Some programs will need different keystroke fakes. For example, editors such as emacs
and vi
are more appropriately faked by sending ^G and escape respectively. In contrast, ftp
would need a simple but real command such as "binary
" to force the client to actually send something to the server since it is the ftp
server that times out, not the client.
The following code achieves an effects similar to the fragment in Chapter 6 (p. 146). Both watch for output containing a pattern where each character is not separated from the next by more than $timeout
seconds.
set status bad interact { -o timeout $timeout inter_return $pattern {set status ok} }
This code works differently but ultimately does much the same thing. The -o
flag makes the subsequent patterns apply to the output of the spawned process. If too much time occurs between characters, the inter_return
action causes the interact
to complete. If $pattern
appears in the output, a status variable is set and the interact
command continues waiting for more characters to arrive (or the timeout to occur). The only significant difference in the code is that the buffering is done internally by interact
rather than explicitly in the user-supplied actions.
A timeout value of −1 is equivalent to infinity. The associated action can never be executed. This is useful when the need for a timeout is not known in advance. Rather than rewriting the interact
command dynamically, the timeout can be suppressed just by an appropriate variable assignment.
interact timeout $timeout $action
if {$need_timeout > 0} { interact timeout 100 $action } else { interact }
The interact
command puts the terminal into raw mode so that all characters can pass uninterpreted to the spawned process. When a pattern matches, actions are executed in raw mode as well. Usually this works well. Most actions do not depend on the terminal mode. For example, the following commands are all terminal-mode independent:
set a [expr 8*$a] send "k " send_user "hello "
You may be surprised that the last command works correctly since it includes a newline. Normally, newlines become linefeeds in raw mode. However, the send_user
command automatically translates newlines to carriage-return linefeed sequences when the terminal is in raw mode. (See Chapter 8 (p. 197) if you want to disable this.) Thus, you can write such commands and not worry about the mode.
Some commands are mode dependent. Here are three example commands, each of which is mode dependent.
system cat file exec kill -STOP 0 expect -re "(.*) "
The first command (”system cat ...
“) executes a program that writes directly to the standard input and output. The program assumes the terminal is in cooked mode and will misbehave if it is not. A common symptom of this misbehavior is displayed in the following output:
this is line one this is line two this is line three and so on
The next command (exec kill ...
) suspends the Expect process, placing the user back in the original shell. Shells that do not force cooked mode will behave incorrectly, leaving processes to run in raw mode when they expect cooked mode.
The last command (expect ...
) is intended to read a single line in cooked mode. (This is similar to "gets stdin
“.) Not all expect
commands require cooked mode. In fact, the example on page 335 was specifically intended to work in raw mode. However, when accepting long strings via expect_user
, it is helpful to be in cooked mode to allow the user to edit the strings.
The expect
command above checks for the newline character explicitly. This could be changed to look for either the linefeed or return character, but the cooked mode editing would still be lacking. The only way to obtain that is to go to cooked mode.
The simplest solution to fix all of these is to use the -reset
flag when defining the action. For example, to define that ^Z (ASCII 32 in octal) suspends the Expect process, you can write the following:
interact -reset " 32" {exec kill -STOP 0}
The other actions could be written similarly. When the actions return, the mode is reset in the other direction—back to raw.
The -reset
flag does not specifically force the terminal into cooked mode. Rather, it forces the terminal into whatever mode was set just before the interact
command was executed. This allows you to set up your own (however bizarre) definition of a “normal” mode and flip between it and raw mode inside of interact
and actions.
Unfortunately, this ability to set the “normal” mode means that -reset
may not have the desired effect inside of an interact
command that was in turn invoked from another interact
. If you want cooked mode in such cases, you have to explicitly set it using stty
in the action. Fortunately, these kind of situation is extremely uncommon.
If you do explicitly set the terminal mode inside an action without using -reset
, reset the mode before returning to the interact
. If you do not reset it, interact
will continue running with your new terminal modes.
In Chapter 8 (p. 206), I mentioned that characters can be lost when terminal parameters are modified. This problem carries over to using -reset
as well, since -reset
potentially modifies terminal parameters. Fortunately, users do not type very quickly so this is rarely a problem; conceivably, however, this problem could arise if interact
were used as a noninteractive filter. If you need absolute assurance that characters will not be lost, do not use the -reset
flag.
The following fragment demonstrates how -reset
could be used to prevent a user from entering certain commands. For example, suppose you want to provide an interface to another program that allows two commands that you would rather people not enter. You want to allow any others, just not these two.
Consider the following reasonable-looking but incorrect approach:
set badcmds "badcmd1|badcmd2" ;# WRONG interact -re "$badcmds.* " { ;# WRONG put "command not allowed " ;# WRONG } ;# WRONG
But as with the earlier modem monitor script, users can enter command-line editing characters to force the pattern to fail. For example, "ba
<backspace>adcmd1
" does not match the interact
pattern and will be accepted by the spawned process.
One solution is to recognize the program prompt and shift into cooked mode while the user enters a command. Any command not in badcmds
will be allowed. This algorithm could also be reversed to allow only a set of good commands.
set badcmds "badcmd1|badcmd2" interact { -o -reset -nobuffer -re $prompt { expect_user { -re "^$badcmds.* " { puts "Command not allowed " } -re "(.*) " { send "$expect_out(1,string) " } } } }
This script can also be written as an expect
loop with an interact
inside of it—similar to the one above but “inside out”. How you structure the script is not that important. What is important is that you use cooked mode to read the results of potentially edited input. In general, if the spawned process is using cooked mode, then your script should too. Conversely, if the spawned process is using raw mode, your script should also.
There are some programs that provide much more sophisticated interfaces than what can be supported in cooked mode. In general, trying to follow what they are doing can require a lot of work. This is unfortunate but understandable—they are internally complicated and any script that attempts to track them will be also.
Modify the Dvorak keyboard script (page 325) to so that it uses a single send
command. Use an array to provide the mapping between input and output. Can you tell any difference in speed between the two implementations?
On page 330, I showed a script that responded to an inactivity message. However, some systems do not print a warning message. Rewrite the script so that it does not require a warning message. Experiment with resetting the timer by sending a null or a ^Q or a space-delete sequence.
Modify the script
script (on page 334) so that it strips out carriage returns. Does "stty -ocrnl
" help? How about "tr -d ' 15'
“?
Write a script to boot and simultaneously allow interaction with an auxiliary processor such as a vxWorks board. Make the script respond appropriately if the board hangs for more than one minute or if the board is power-cycled behind Expect’s back.
Write a script that allows you to repeat a command in the shell (or any program) by pressing a single function-key. Write a second version that suspends this behavior when the shell is not prompting.
Write a script that provides “hotkey” service in the style of a DOS TSR, which will temporarily bring a background process to the foreground. Allow triggering either by keypresses or specific times.
[54] The catch around the exec catches systems on which rm complains if no file is present. POSIX standardizes the -f flag to prevent such a diagnostic, but some systems complain anyway. Use catch to be maximally portable.
3.143.4.181