Question

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 terminates 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.

Was it helpful?

Solution 2

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.

OTHER TIPS

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.

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:

  1. Host keys: Either connect once manually, or insert the key directly in the ssh known_hosts file.
  2. 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)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top