How to Use Expect with Optional Prompts

How to use expect with optional prompts?

You can expect multiple things:

expect { 
"prompt2" {
send "pass2"
expect "prompt3"
send "pass3"
}
"prompt3" {
send "pass3"
}
}

How to make a string as an optional to wait for it in an expect script?

set password [lindex $argv 0]

spawn asadmin --user admin change-admin-password

# First time, when we see the password, we are simply typing 'return' key
expect "password"
send "\n"

expect {
"password" { send "$password\n"; exp_continue }
-ex "Do you trust the above certificate \[y|N] -->" {send "y\n";exp_continue}
timeout { puts "Timeout happened." }
eof { exit }
}

As you can see, exp_continue will help us in getting what you need.

If expect sees password, it will send the password value. Notice the use of exp_continue in there.
It will cause the expect to run again. So, the expect will see the password twice and if suppose, expect sees the question, it will send 'y\n'. If it sees eof before, then script will exit.

Please note that I have kept the first expect statement with password separately outside. The reason being is nothing but the value we are sending is different for the first time alone.

Also note the use of the -ex flag in the expect statement as below.

-ex "Do you trust the above certificate \[y|N] -->" {send "y\n";exp_continue}

It will make the expect to prevent any sort of special pattern matching. It is sufficient to escape the first square bracket alone.

How to expect multiple prompts correctly

While using a regular expression to detect the prompts is the right thing, choosing a good one is tricky when you've got such a wide range of possibilities. For example, I bet that this RE would work:

set multiPrompt {[#>$] }

(It just detects the end of the prompt, and ignores all the stuff before it. There's virtually always a space at the end of the prompt, used to visually separate what users type from the prompt.)

However, the problem is that this RE is fairly likely to match other things. You might instead be better off changing the prompt to a known-and-unique value (typically by setting the PS1 environment variable on the remote device) so that you get reliable detection. Mind you, that's only suitable when you're not exposing the prompts back to users, which is true for some uses of expect and not others…

expect script with optional ssh-agent passphrase prompt?

Yeah, that's a funny one. Since you're using ssh -N, you don't launch any process on the remote host, and the ssh session just sits there. You would just have to sleep for a couple of seconds and hope the tunnel gets established.

set timeout 2
spawn ssh -M -S /var/run/examplecom-mysql-socket -fnNT -L 3307:localhost:3306 someuser@example.com

expect {
# conditional prompt appears in case ssh-agent is not active
"*.ssh/id_rsa':*" { send -- "$SSH_PASSPHRASE\r" }
# wait until the timeout, then carry on with the rest of the script
timeout
}

set timeout -1
# ...

Or, remove -N, then you can expect to see the prompt on the remote host.

spawn ssh -M -S /var/run/examplecom-mysql-socket -fnT -L 3307:localhost:3306 someuser@example.com
# ..........................................^^^^

expect {
# conditional prompt appears in case ssh-agent is not active
"*.ssh/id_rsa':*" { send -- "$SSH_PASSPHRASE\r" }
# I assume your prompt ends with a dollar sign and a space
-re {\$ $}
}

Now you can spawn the mysql command and interact with that.

What is the proper way to script Expect?

When you split it into two separate commands, you force the script to first look for the "yes/no" prompt. It will wait up to $timeout seconds (default 10 seconds). Only then will it look for the password prompt.

Your first idea is the right approach, but you're missing one key command:

expect {
"yes/no" {
send "yes\r"
exp_continue ;# <== this
}
"assword: " {
send "$mypass\r"
}
}

The exp_continue command essentially forces the flow of execution to remain in that Expect command so you can still match the password prompt.

A minor point: idiomatically, use \r (carriage return) as the character for "hitting Enter".

For further learning, look at the expect tag for more information and links to other questions and answers.

Expecting the Unexpected in Expect

Try like this:

set timeout 10; # set a reasonable timeout

# expect and send username/password ...

set success 0
set err_msg ""
expect {
"Login success!" {
set success 1
}
eof {
set err_msg $expect_out(buffer)
}
timeout {
expect *
set err_msg $expect_out(buffer)
}
}

if {! $success} {
send_mail $err_msg
exit 1
}

User input for Expect script

I have used this code before to achieve what you wish to achieve. You can take it as a reference point to write your own version of the code.

#!/usr/bin/env expect -f
set passw [lindex $argv 0]

#timeout is a predefined variable in expect which by default is set to 10 sec
set timeout 60
spawn ssh $user@machine
while {1} {
expect {

eof {break}
"The authenticity of host" {send "yes\r"}
"password:" {send "$password\r"}
"*\]" {send "exit\r"}
}
}
wait
#spawn_id is another default variable in expect.
#It is good practice to close spawn_id handle created by spawn command
close $spawn_id

Source: Expect Wiki

Expect statement prompt

Thanks to Schelte Bron, I now can see exactly how the regex matches the prompts.
It turns out that the prompt has colors and this put all sort of characters before and after the % or #.

To overcome this you can either

  1. overide the prompt PS1 variable
  2. take care of all the hidden characters of the prompt in the regex
  3. capture the prompt when logging in and use it in the regex (seems like what we SHOULD do but this seems a lot of work)

Run expect in debug mode :

expect -d 


Related Topics



Leave a reply



Submit