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
- overide the prompt PS1 variable
- take care of all the hidden characters of the prompt in the regex
- 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
How to Cut First Column (Variable Length) of a String in Shell
How to Write Linux Driver Module Call/Use Another Driver Module
How to Untar a Tar.Bz File in Unix
How to Look Up a Variable by Name with #!/Bin/Sh (Posix Sh)
Using Rsync Include and Exclude Options to Include Directory and File by Pattern
How to Run a Command in a Chroot Jail Not as Root and Without Sudo
Identifying Received Signal Name in Bash
Renaming Lots of Files in Linux According to a Pattern
How to Control a User Systemd Using 'Systemctl --User' After Sudo Su - Myuser
Awk One Liner Select Only Rows Based on Value of a Column
Replacing Environment Variables in a Properties File
How to Extract Characters Between the Delimiters Using Sed
Can You Prevent a Command from Going into the Bash Shell Command History
How to Get the Last Word in Each Line with Bash
Refresh Net.Core.Somaxcomm (Or Any Sysctl Property) for Docker Containers