The Nightmare of Interactive CLIs
Writing shell scripts that get stuck waiting for passwords is a nightmare. You want full automation, but the tool stops and waits for a ‘yes/no’ confirmation or a password. Typical examples include the passwd command, ssh (when keys aren’t set up), or legacy software installers from decades ago.
On an Ubuntu 22.04 server (4GB RAM) I manage, I once had to stay up until 2 AM just to type a password for an ancient FTP backup tool. This tool didn’t support passing passwords via parameters. Waiting to type a few characters every night was a massive waste of time. That’s when I turned to expect.
Why Do Standard Methods Fail?
Before using expect, I tried a few shortcuts that didn’t work:
- Using Pipes (
echo "pass" | command): This often fails withsudoorpasswd. These security tools read directly from/dev/tty(the terminal) rather than looking atstdin. - Using Flags (
-y):apt install -yworks great. But in reality, many internal scripts or legacy software don’t offer such “generous” flags.
Expect: Playing the Role of the User
Expect isn’t just a trick; it’s a real program. It works based on a conversational script: “I wait for you to say A, and I’ll respond with B.”
After six months of production use, here are my core takeaways:
- Pros: Handles all types of interaction. Excellent branching logic: exit if “Error” is detected, continue if “Success” appears. Specifically, the
autoexpecttool can even write scripts for you. - Cons: Tcl (Tool Command Language) syntax feels a bit strange to Bash users. You also face security risks by storing passwords in plain text files.
Installation and Core Commands
On Ubuntu/Debian, installation takes just 3 seconds:
sudo apt update && sudo apt install expect -y
Most expect scripts revolve around these 4 commands:
spawn: Starts the process (e.g., opening an SSH connection).expect: Waits for a specific keyword to appear on the screen.send: Inputs data (simulating keystrokes).interact: Hands control back to the user when needed.
Example 1: Automating Password Changes in a Snap
The passwd command usually requires entering the password twice. The following change_password.exp script handles it cleanly:
#!/usr/bin/expect -f
set user [lindex $argv 0]
set password [lindex $argv 1]
spawn passwd $user
expect "Enter new UNIX password:"
send "$password\r"
expect "Retype new UNIX password:"
send "$password\r"
expect "password updated successfully"
expect eof
Tip: Don’t forget the \r at the end of the send string. It’s equivalent to pressing Enter. Without it, the script will hang indefinitely.
Example 2: Bypassing SSH Prompts Without Keys
While SSH Keys are the standard, sometimes you’re forced to log in with a password on old network devices. Here’s how to handle it:
#!/usr/bin/expect -f
set timeout 10
spawn ssh [email protected]
expect {
"yes/no" {
send "yes\r"
exp_continue
}
"password:" {
send "Secret123\r"
}
}
expect "$"
send "uptime\r"
send "exit\r"
expect eof
Here, I use a branching structure. If the server asks to confirm the fingerprint (yes/no), the script answers ‘yes’ and then continues to wait for the ‘password’ prompt.
Autoexpect Trick: Let the Machine Write the Code
Writing scripts manually is prone to typos. I often use autoexpect to save 80% of the time. Just run:
autoexpect ./your_script.sh
Then, interact with it like a real person. autoexpect will record everything and export it to a script.exp file. You just need to open that file and trim the unnecessary parts.
Security Concerns: Don’t Be Complacent
Storing passwords in files is a major risk. To stay safe, I always follow two golden rules:
- Restrict Access: Run
chmod 600 script.expimmediately. Only you should have permission to read this file. - Use Environment Variables: Never hardcode passwords. Pass passwords from a secret management system like HashiCorp Vault or encrypted environment variables.
After implementing Expect, my backup jobs are 100% automated, reducing the error rate from typos to zero. If you’re managing “stubborn” systems, Expect is the weapon that will save you from boring night shifts.

