In the context of scripting, Expect is a two things. It is a
program, and a
command within the program.
Expect: The Program
Expect is a complex scripting program written in Tcl.
History
Expect was designed by Don Libes in 1990, with its original intent being to automate the initial login and command in a telnet session. This program became one of the first widespread uses of Tcl, and helped popularize it.
Function
The concept underlying Expect is to automate any program that requires user input. A simple example would be a script that spawns a telnet to a server, waits for the "Login:" prompt, sends the login, waits for the "Password:" prompt, then sends the password. Of course, connecting to a server is just a taste of Expect's power - almost any sort of (command line) automated testing or system administration task can at least be partially automated with Expect.
Expect is very fast - the glob pattern techniques it uses are incredibly efficient. In general, Expect is always faster than the program it deals with.
Expect also saves a lot of time and money for system administrators, software testers, and even gamers.
Weaknesses
Expect is picky. Sometimes, a simple hiccup from a program can send Expect into a tail spin. Between send slow, buffers, poorly written glob patterns, timeouts, terminal width / terminal length, and many other oddities, a superbly written Expect script can come tumbeling down without a moment's notice.
To make matters worse, Expect is written in Tcl, which can easily devolve into spaghetti nigthmares when a project exceeds the size of "simple telnet script", lacking any semblance of organizational structure. Expect scripts can be very difficult to read, making them hard to reuse.
I recommend avoiding Expect if you have not worked with Tcl, or your only programming language is something like C++. Between the frustrating simple errors (not referencing a variable in Tcl) to the eldritch timeouts of Expect, it can be a very difficult endeavour.
A Sample Expect Script
The script below telnets to a "targetIP" and logs in.
# this is a comment
set targetIP 172.17.XXX.49
set login Angst
set password wouldntyouliketoknow
set hostname b4-d5
spawn telnet $targetIP
expect "ogin"
send "$login\r"
expect "sword"
send "$password\r"
expect $hostname
send "cd /usr/bin\r"
expect $hostname
interact
Let's take this script apart. To do that, you need to know a little bit of
Tcl. I'll explain all the
Tcl required to understand this script, and write scripts similiar to it.
This script opens with a comment. A comment in
Tcl is made by typing #, followed by whatever it is what you want to say. It is similiar to
C's "
//."
There are a few slight limitations - #! (insert file path here) is a compiler instruction. That tells Linux "hey, run me with (insert file path here)." Another limitation is no comment should be around the expect command - for example:
# A comment here is safe
expect {
# Never put a comment here! This is an error!
"uh oh" {
puts "We've got big problems with this!\n"
}
}
# A comment here is safe
# # # # # # # # ############ This comment is ugly, but safe##
Next, we have four lines beginning with "set..."
In order to understand these lines, you need to know about how one creates a variable in Tcl. In Tcl, a variable is defined without a type (everything is a string), unlike C where you define variables with their type (instead of int number or string letters = 'letters', we'd use set number 0 and set letters "letters"). If you try to set it and it doesn't already exist, one is made. In these four lines, a variable is made for the IP Address, the Login, the Password, and the host's name.
After we initalize our variables, we have the line:
spawn telnet $targetIP
Basically, this tells expect to open up a program named telnet, and pass it the value inside the variable "targetIP". If you're new to
Tcl, it may seem odd I bothered to write "the value inside the variable." This is because if I were to have typed:
spawn telnet targetIP
I would tell expect to go make a program named telnet and send it the phrase "targetIP"! This would compile, but we'd get a runtime error - no work would be done because telnet would have no idea what to do (unless you have
DNS configured and there is a host named targetIP).
At this point, we have our variables all ready to go and we've opened a session of telnet.
We come across a command that I've gone into more detail later in this node:
expect "ogin"
To keep from repeating the definition of the expect command (which is below), I'll mainly explain the
argument "ogin." ogin (notice the o is not capitalized) is part of the phrase "login." The "l" is kept off this expect command because a capital L and a lower case l are different things to expect in this case. If this were written as the following:
expect "login"
and telnet had sent me "Login:", then Expect would simply sit there until the expect command timed out. expect (the command) times out when the number of seconds equal to the timeout variable has passed since the expect command was issued. After an expect command times out, the expect script carries on. If we set timeout to -1, then the expect command never times out. Of course, telnet will probably timeout at some point. If we had written login with a lowercase 'l' or an uppercase 'L' when we needed the opposite, then the script would be slower. Because the default timeout is set to 10 seconds, our scripts would take 20 seconds longer to run.
Now, we're at:
send "$login\r"
Assuming that the rest of the script worked properly, telnet is currently prompting us with "Login:" This send command will not be reached before the prompt comes up because of the previous line (expect "ogin"), which told expect to wait for the phrase "ogin" - and unless something very fishy is up, "ogin" will not appear anywhere without "Login:". The send command above responds to the prompt with the value inside the variable "login," and the return carriage.
Important!The return carriage is different from a \n! A \n means "new line," while the \r means "send the enter...this is the data ye seek." A common mistake is to use "\n" as a replacement \r - what it does, is print to the screen whatever you meant to send, and never actually send it to the process.
After the variable "login" answers the prompt, we enter another expect. This time, the expect is looking for "sword."
Sword? Yes, it's really odd, but sword works perfectly in this case. sword is part of the word "Password" - so when the phrase "sword" is seen, odds are (assuming you're not on a MUD) it's because telnet has come across a password prompt. We answer this prompt in a similiar manner as the login.
Now, we come across another expect looking for the host's name. Typically, that's a piece of the prompt used in a telnet session once we're past the login / password screens. For example:
Angst@b4-d5>
Once this expect finishes (either through the prompt coming up again, or it timing out), we send a change directory command (cd), followed by "interact." We could place an expect command before the interact, but that would be redundant - the interact command hands control of the spawned process to the user and tells expect "hands off!" Because we biological processes don't have lightning speed capabalities like expect (any human would have the sensibility to wait for a prompt anyway), we won't end up in a situation where we send our next command without a prompt coming up.
There you have it, a very practical and fast script. The commands interact, send, expect, and spawn are the four core commands which give this program its power.
Invoking Expect
The above script is very nice, but to run it you need to do one of two things:
1 - Type in expect (your script name here)
2 - Add to the very tip-top of your script "#! /usr/bin/expect" (or wherever you keep expect), type in chmod 744 (your script name here), then go to the file with the script in it, and type ./(your script name here)
Note to new UNIX users: in option two, "chmod" changes what users are allowed to do the file. 744 is an argument that basically says "you're allowed to
execute this now."
Expect: The Core Command
"expect" is one of the core commands of Expect. Expect is basically a control structure that waits for a pattern, and then acts accordingly.
Syntax
In the following example:
expect "hello"
This script waits for the phrase "hello" from the spawned process. It will wait until the
timeout variable has elapsed, or "hello" is found in the buffer. "hello" will succesfully match anywhere it is found in the
buffer - we told Expect to look for the letters 'h' 'e' 'l' 'l' 'o' adjacent to each other, so in these cases
hello
Othello
34hello
The expect command will stop waiting. Othello and hello both tell the expect command to stop waiting because Expect sees it as "Ot
hello" and "
hello." The 34 in front of "34hello" is discarded, as is the "Ot."