Expect with expect

Wouldn’t it be nice to program a script that expects a certain line of text then sends a predetermined string? How about copy your ssh id to 40 different RPi units without typing the command for every one? Expect is that sort of program we all have been dreaming about. Just look at this script:

#!/usr/bin/expect -f
 
set timeout 3
set serv [lindex $argv 0];
 
spawn ssh-copy-id pi@$serv
sleep 4
expect "Are you sure you want to continue connecting (yes/no)?"
send "yes\r"
expect "pi@{$serv}'s password: "
send "p@ssw0rd\r"
interact

When this block of code is saved as copy_pi.sh and invoked as ./copy_pi.sh 192.168.1.42, it will attemp to connect as the pi user, copy the current public key, say yes to the prompt, send the password (provided it is escaped properly), and drop you into the process you spawned.

Now, let’s look at the script in more detail as to what expect has to offer.

#!/usr/bin/expect -f

This line has to be the absolute path of expect with the -f switch. This is to tell the program that it is being passed a file with a list of commands. On Debian/Fedora, this path is where expect is located. On other systems, you will need to run which expect to find the absolute location.

set timeout 3
set serv [lindex $argv 0];

The first line tells expect to wait up to 3 seconds for a new line before it times out. The second line is grabbing the variable being passed into this script. Normally, one would think arg 0 would be the name of the program (i.e. copy_pi.sh); however, expect captures the variables into $argv ($argc is the length of the variables).You can also specify a range of variables into one: set range [lrange $argv 0 2];

spawn ssh-copy-id pi@$serv
sleep 4

This is we are initializing the process of copying our ssh key to $serv.
This is the part we would normally be typing ourselves. sleep 4 tells expect to wait 4 seconds before continuing.

expect "Are you sure you want to continue connecting (yes/no)?"
send "yes\r"
expect "pi@{$serv}'s password: "
send "p@ssw0rd\r"
interact

The expect line could either be regex or what will be exactly shown. Since I know exactly what will appear in the process, I have chosen to go that route. The send command is transmitting the text into the spawned program. Special characters such as & need to be escaped with a backslash. The \r simulates a carriage return (enter key). Since the ssh-copy-id is completed by the time interact is called, you will not be in the remote pi’s shell. I have sometimes found it necessary to include interact so that the password is actually sent.

expect is a great tool that uses TCL syntax. expect has prooved useful to me many times over – especially when I had to re-key my puppet deployment after Heartbleed.