Quick introduction about Expect

Expect is a very useful tool to automate work with interactive applications, while also it is a very old. It is based on Tcl language which was created at 1988, and is not seen much nowdays except in test domain.

The expect script is written with Tcl, and the syntax is a little wired compared with other popular language nowdays.

The only scenario I used expect many years ago was to automatic ssh login. And after I know how to use SSH key to login, I abandoned it.

I came up to expect recently because of the same scenario, SSH login. I need to ssh jump several times to reach the target server, and due to the system restriction, the ssh key can't be saved permently and will lost after reboot.

The scenario is a little complicated compared with my old case. In order to know how to better write the expect script, I read the Exploring Expect: A Tcl-based Toolkit for Automating Interactive Programs book. The book is nice-written and worth reading if you want to learn little about Tcl or expect.

Here some tips I learn from the book.

The first import thing is how to debug

The simple ways is to use -d option. Here are several ways:

# 1: add -d with expect command
$ expect -d sample.exp

# 2: add -d at the first line of expect script
#!/usr/bin/env expect -d

# 3: add exp_internal 1 in the script
spawn telnet abc.net
exp_internal 1

expect "Login: "
send "don\r"
expect "Password: "
send "swordfish\r"

# 4: you can also use -D 1 option for expect to trigger gdb liked debugger
$ expect -D1 sample.exp
1: expect "hi\n"

dbg1.0>

# 5: use strace <level> to print statments before excuted
# like the set -x in shell
expect -c "strace 4" sample.exp

Expect with pattern and actions

You can use expect with may patterns and actions, just like switch in C:

expect {
    "hi" { send "You said hi\n"}
    "hello" { send "Hello yourself\n"}
    "bye" { send "That was unexpected\n"}

    # a special pattern default(without quotation) is for timeout and EOF
    default {send "timeout or eof\n"}
}

Expect command support both globing and regex for pattern matching. The options are -gl- and -re, the default is globing(-gl).

The matched string is saved in expect_out(0,string), and any matching and previously unmatched output is saved in variable expect_out(buffer).

The command exp_continue allows expect itself to continue executing rather than returning as it normally would.

Spawn new process

You can spawn command to create new process like this:

spawn ftp abc.net
expect "Name"
send "anonymous\r"
expect "Password:"
send "don@libes.com\r"

But if the command is dynamic, a variable for example, you need to use with eval command. Eval in expect is like eval in shell, it will expand the variable and execute the command.

#!/usr/local/bin/expect --
set timeout [lindex $argv 0]

# spawn the command from argv with eval
eval spawn [lrange $argv 1 end]
expect

Interact with spawn process and continue

The simple usage for the interact it to return the control to the user.

Actually interact provides the functions like expect with patterns and actions.

Simple example is:

spawn ftp abc.net
# ...

interact {
    "~d"        {puts [exec date]}
    "~e"        exit
    "foo"       {puts "bar"}
}

When use input “~d”, date command will be executed, and the result is echoed, and so on.

It also provides the function to break or continue execution like this:

while {1} {
    interact "+" break "-" continue
}

In the above loop, if a user presses “+“, the interact returns and the loop breaks. If the “–” is pressed, the interact returns, and the while loop continues.

Signal handling

The trap is used to handle singal, simple example is like this:

trap intproc SIGINT
trap {
    send_user "bye bye"
    exit
} SIGINT

And a special singal SIGWCH is for window size change, the handler is like this:

trap {
    set rows [stty rows]
    set cols [stty columns]
    stty rows $rows columns $cols < $spawn_out(slave,name)
} WINCH

#tcl #expect #ssh