The Barrier of Interactive Prompts
Standard shell scripting is fantastic for sequential tasks, but it often hits a brick wall when a command demands real-time interaction.
I’m talking about those moments when a script stops dead in its tracks, waiting for you to type a password, confirm a license agreement, or choose between ‘yes’ and ‘no’. Commands like ssh, ftp, or even custom application installers are notorious for ignoring standard input redirection (like echo "password" | command) because they look specifically for a TTY (teletype) session for security.
This is where Expect becomes a lifesaver. Originally developed in the early 90s, Expect is a tool built on top of the Tcl language designed specifically to talk to interactive programs. It watches for a specific string (the “prompt”) and then sends a pre-defined response. It’s the closest thing to having a virtual robot sitting at the terminal typing for you. I’ve found it indispensable for legacy systems where APIs aren’t available and SSH keys aren’t an option.
Getting Expect on Your System
Before we can start automating, we need the tool. Expect isn’t always pre-installed on minimal Linux distributions, but it’s available in almost every major repository. The installation is straightforward and takes less than a minute.
On Debian, Ubuntu, or Linux Mint:
sudo apt update
sudo apt install expect -y
On RHEL, Fedora, or AlmaLinux:
sudo dnf install expect -y
To verify the installation, you can simply check the version. My current setup is running version 5.45.4, which is rock stable for everything we’re about to do.
expect -v
Crafting Your First Expect Script
An Expect script usually starts with a shebang pointing to the Expect interpreter. While it borrows syntax from Tcl, you don’t need to be a Tcl expert to write effective automation. Most scripts revolve around four primary commands: spawn, expect, send, and interact.
The Core Commands: Spawn, Expect, and Send
Let’s look at a classic example: automating an SSH login. This is a common pain point when dealing with older network appliances that don’t support public key authentication.
#!/usr/bin/expect -f
# Set variables
set timeout 10
set host "192.168.1.50"
set user "admin"
set password "Secret123!"
# Start the process
spawn ssh $user@$host
# Look for the password prompt
expect "password:"
# Send the password followed by a carriage return
send "$password\r"
# Hand over control to the user
interact
In this snippet, spawn starts the SSH command. The expect command waits for the string “password:” to appear in the terminal. Once found, send pushes the password string followed by \r (which simulates the Enter key). Finally, interact keeps the session open so you can take over manually; without it, the script would finish and the SSH session would close immediately.
Handling Multiple Prompts and Logic
Real-world scenarios are rarely that simple. Often, you’ll encounter different prompts depending on whether it’s the first time you’re connecting or if an error occurs. I prefer using a switch-like structure within the expect block to handle these variations.
spawn ssh $user@$host
expect {
"yes/no" {
send "yes\r"
exp_continue
}
"password:" {
send "$password\r"
}
timeout {
puts "Connection timed out!"
exit 1
}
}
The exp_continue command is a neat trick. It tells Expect to keep waiting for more prompts after sending a response. This allows you to handle the SSH fingerprint confirmation (“yes/no”) and then immediately handle the password prompt that follows.
Passing Arguments from the Shell
Hardcoding passwords and hostnames is a bad habit. You can pass arguments from your Bash shell into an Expect script using the argv array. This makes your scripts reusable across different environments.
#!/usr/bin/expect -f
set user [lindex $argv 0]
set host [lindex $argv 1]
set password [lindex $argv 2]
spawn ssh $user@$host
expect "password:"
send "$password\r"
interact
You would call this script like this: ./myscript.exp admin 10.0.0.1 mypass. Just be careful with this approach, as passwords passed as arguments might show up in your shell history.
Validating Scripts and Ensuring Reliability
Automation is only useful if it’s reliable. I’ve seen many scripts fail because they were too “brittle”—meaning they expected a prompt that changed slightly due to a software update or a different terminal color setting.
Debugging with exp_internal
When an Expect script hangs, it’s usually because it’s waiting for a string that never appears. To see exactly what Expect is seeing, add exp_internal 1 to the top of your script. This will dump the internal matching process to the terminal, showing you exactly where the mismatch occurs.
Setting Realistic Timeouts
By default, Expect has a 10-second timeout. For long-running tasks like database migrations or remote installations, this is often too short. You can set a global timeout with set timeout 300 (5 minutes) or use -1 to wait indefinitely. However, waiting indefinitely is risky; if the program crashes, your script will hang forever.
The Importance of Local Testing
After managing 10+ Linux VPS instances over 3 years, I learned to always test thoroughly before applying to production. This is especially true for Expect scripts. A small typo in the expected string can lead to a script that enters the wrong password multiple times, triggering a security lockout on your servers. I always recommend testing on a local virtual machine or a staging container first.
Alternative: Using Autoexpect
If you’re dealing with a particularly complex interactive tool, manually writing the script can be tedious. There’s a tool called autoexpect that acts like a macro recorder. You run autoexpect ./your_command, perform the interactive steps manually, and it generates a script.exp file for you. It’s usually a bit messy and over-commented, but it’s a great starting point for complex workflows.
Integrating Expect into your toolkit transforms how you handle Linux administration. It bridges the gap between manual intervention and total automation, allowing you to scale your workflows without being held back by legacy interactive prompts. Just remember to keep security in mind—protect your script permissions (chmod 700) and avoid plain-text passwords whenever a more secure alternative like SSH keys or vault tokens is available.

