When working with Expect scripts for SSH automation, the "spawn id exp4 not open" error typically occurs when the script tries to interact with a process that has already terminated. From the debug output, we can see this happens after:
Connection to 10.10.10.10 closed.
expect: read eof
expect: spawn id exp4 not open
while executing
"expect eof"
The core issue is that your Expect script continues trying to interact with the SSH session after it has already closed. This happens because:
- Your script has multiple password attempts configured
- The SSH session terminates after successful authentication and command execution
- Subsequent expect commands still try to match patterns
Here's a more robust version of your script that handles these cases:
#!/bin/bash
(( $# != 1 )) && { echo >&2 "Usage: $0 \"[COMMAND]\""; exit 1; }
servers_addresses=(10.10.10.10)
password_attempts=(
"Wrong_Password_11111"
"Correct_Password"
"Wrong_Password_33333"
)
for server_address in ${servers_addresses[@]}; do
expect <<EOF
set timeout 10
spawn ssh -t root@$server_address "$*"
expect {
-re "Are you sure you want to continue connecting.*" {
send "yes\r"
exp_continue
}
-re "password:" {
foreach pass $password_attempts {
send "$pass\r"
expect {
-re "password:" { continue }
-re "Permission denied" { continue }
eof { exit 0 }
timeout { exit 1 }
}
}
exit 1
}
eof { exit 0 }
timeout { exit 1 }
}
EOF
done
Better Expect Pattern Matching
The improved script uses regular expressions (-re) instead of simple glob patterns, making it more reliable:
-re "Are you sure you want to continue connecting.*"
-re "password:"
Proper Session State Handling
The script now properly handles different session states:
expect {
-re "password:" { continue }
-re "Permission denied" { continue }
eof { exit 0 }
timeout { exit 1 }
}
For most production use cases, you should consider using SSH keys instead of passwords. If you must use passwords, here's a simpler version:
#!/bin/bash
(( $# != 1 )) && { echo >&2 "Usage: $0 \"[COMMAND]\""; exit 1; }
servers_addresses=(10.10.10.10)
for server_address in ${servers_addresses[@]}; do
expect <<EOF
set timeout 10
spawn ssh -o StrictHostKeyChecking=no -t root@$server_address "$*"
expect {
"password:" { send "Correct_Password\r" }
timeout { exit 1 }
}
expect {
eof { exit 0 }
"password:" { exit 1 } # If we get password prompt again, auth failed
timeout { exit 1 }
}
EOF
done
When debugging Expect scripts, these techniques can help:
# Add debug output
exp_internal 1
# Increase timeout for debugging
set timeout 30
# Log all interaction
log_file expect_debug.log
While this solves the technical issue, remember:
- Never store passwords in plaintext scripts
- Consider using SSH key authentication instead
- Use configuration management tools for production environments
When working with Expect scripts for SSH automation, you might encounter the frustrating "spawn id exp4 not open" error. This typically occurs when your Expect script tries to interact with a spawned process that has already terminated. Let's examine the root causes and solutions.
Consider this common use case where the error appears:
#!/bin/bash
servers_addresses=(10.10.10.10)
for server_address in ${servers_addresses[@]}; do
expect <
The error appears because:
- The SSH session terminates after successful authentication and command execution
- Subsequent expect commands try to interact with a closed connection
- The script doesn't properly handle the session lifecycle
Here's a more robust implementation that handles various cases:
#!/usr/bin/expect -f
set timeout 20
set cmd [lindex $argv 0]
set password1 "Correct_Password"
set password2 "Wrong_Password_22222"
set password3 "Wrong_Password_33333"
spawn ssh -t root@10.10.10.10 "$cmd"
expect {
"Are you sure you want to continue connecting (yes/no)?" {
send "yes\r"
exp_continue
}
"s password:" {
send "$password1\r"
exp_continue
}
"Permission denied" {
send "$password2\r"
exp_continue
}
eof {
exit 0
}
timeout {
exit 1
}
}
The improved script includes:
- Proper timeout handling
- exp_continue to keep the expect block active
- Explicit eof handling
- Clear exit conditions
For simpler cases, consider using sshpass:
#!/bin/bash
servers_addresses=(10.10.10.10)
for server_address in ${servers_addresses[@]}; do
sshpass -p 'Correct_Password' ssh -o StrictHostKeyChecking=no root@$server_address "$@"
done
When debugging Expect scripts:
exp_internal 1 # Enable verbose debugging
log_user 0 # Disable output if needed
For production environments:
- Use SSH keys instead of password authentication
- Implement proper error handling
- Consider using Ansible or other automation tools for complex scenarios
- Always test scripts thoroughly before deployment