I am struggling with the classic problem of typing password automatically in ssh, and like everybody else I am stumbling in the dark regarding expect. Finally I cobbled together a script that kinda work:
#!/usr/bin/expect -f
# command line args
set user_at_host [lrange $argv 0 0]
set password [lrange $argv 1 1]
set timeout 1
# ssh command
spawn ssh -S ~/.ssh/tmp.$user_at_host -M -N -f $user_at_host
# deal with ssh prompts
expect {
"*yes/no*" { send "yes\r" ; exp_continue }
"*assword:" { send "$password\r" ; exp_continue }
}
This script termina开发者_如何学Gotes only thanks to the timeout 1
line, without it it simply hangs, and will terminate only by user interaction (^C
).
When the spawn
line was a straight forward ssh command, the script terminated right away, however this is not your straight forward ssh. The thing that might be different is the -f
option that make it run in the background (but I tried the script without it to no avail).
I read that interact
or expect eof
might help, but I wasn't able to find the correct incantation that will actually do it.
My question (I think) is How to make an expect script, that spawn a background process, terminate without a timeout?
Edit: I should have expected (no pun intended) the "use passwordless ssh authentication" answer. While this is a sound advice, it is not the appropriate solution in my scenario: Automatic testing a vanilla installed system in a trusted environment, where adding trusted keys to the image is not desirable / possible.
You probably want:
expect {
"*yes/no*" { send "yes\r" ; exp_continue }
"*assword:" { send "${password}\r" }
}
expect $the_prompt
send "exit\r"
expect eof
UPDATE
I missed that you are sending a command via ssh. I think all you need is this:
spawn ssh a@b foo bar baz
expect {
"*yes/no*" { send "yes\r" ; exp_continue }
"*assword:" { send "${password}\r" }
eof
}
You'd hit eof
when the foo
command completes.
OK, so I found a permutation that seem to work --
First I need a wrapper
script that will give an indication when done:
#!/bin/bash
"$@"
echo "done"
Then the expect script becomes:
#!/usr/bin/expect -f
set user_at_host [lrange $argv 0 0]
set password [lrange $argv 1 1]
# no need for timeout 1
set timeout 60
# use the wrapper
spawn wrapper ssh -S ~/.ssh/tmp.$user_at_host -M -N -f $user_at_host
expect {
"*yes/no*" { send "yes\r" ; exp_continue }
"*assword:" { send "$password\r" ; exp_continue }
# use the wrapper
"done" { exit }
}
Thanks for Douglas Leeder (voted up) and glenn jackman (voted up) for the helpful advice. I will gladly un-accept this answer, and accept any more elegant answer, perhaps one that do away with the wrapper script.
Thank you all for your attention.
This loop:
expect {
"*yes/no*" { send "yes\r" ; exp_continue }
"*assword:" { send "${password}\r" ; exp_continue }
}
Can't terminate any way except timeout or EOF; the two matching lines will exp_continue
, so do round the loop again.
Going into the background means basically forking; the parent dies, and the child continues the connection.
Personally, I'd solve the interactive elements differently:
- Host keys: Either connect once manually, or insert the key directly in the ssh known_hosts file.
- Password: I'd use private/public key authentication. Either use an agent to store the key, or have a password-less key.
the way i did it:
set user "****"
set host "127.0.0.1"
spawn shh $user@$host
expect "password: "
send "**??54"
expect "$"
interact
(that answers your how to use interact method)
精彩评论