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.