Expect doesn't recognize command "timeout" in loop - expect

I have an expect script to check login status, if console showing "poplar login", script will send username (no password) and wait 180s for system ready.
Once system ready it will check is the console showing "root#poplar" if yes, it will break and send success message. If not, expect will be timeout and go back to "poplar login"
my script
set timeout 2
expect {
"poplar login" {
send "root\r";
sleep 180; send "\r" ;
send "\r" ;
exp_continue
}
"root#poplar"
timeout { send "\r" ; exp_continue }
}
send_user "login success!\n"
Somehow the timeout is always not read by expect and the log is like follow
expect: does"\r\n\u001b[r\u001b[m\u001b[2J\u001b[H\u001b[?7h\u001b[?1;4;6l
\u001b[?1049h\u001b[4l\u001b[?1h\u001b=\u001b[0m\u001b(B\u001b[1;70r\u001b[H
\u001b[2J\u001b[H\u001b[2J"(spawn_id exp5) match glob pattern "poplar login"? no
"root#poplar"? no
" send "\r" ; exp_continue "? no
expect: timed out
login success!
send: sending "reboot\r" to { exp5 }
Please assist, thanks

Change
"root#poplar"
timeout { send "\r" ; exp_continue }
to
"root#poplar" {}
timeout { send "\r" ; exp_continue }
or it would be handled as
"root#poplar" timeout
{ send "\r" ; exp_continue }
That's why you see the debug message
" send "\r" ; exp_continue "? no
because the part { send "\r" ; exp_continue } is handled as a PATTERN.

Related

How does Expect script determine contents of expect_out

I am trying to write a script will provide credentials to a vpn with one caveat where VPN requires that I provide OTP.
This is my script:
#! /usr/bin/expect
set vpn [ lindex $argv 0]
set group [ lindex $argv 1]
set user [ lindex $argv 2]
set secret [ lindex $argv 3]
log_user 2
spawn nmcli con up $vpn --ask
expect {
"GROUP:" {
send "$group\r"
exp_continue
}
"Username:" {
send "$user\r"
exp_continue
}
Password: {
send_user "\nProvide RSA OTP:"
expect_user -re ":(.*)\r"
set otp $expect_out(0,string) <--------- Error!!!
set pass "$secret$otp"
send "$pass\r"
exp_continue
}
"Connection successfully activated" {
send_user "Connected to VPN\r"
}
"Login failed" {
send_user "Login failed\r"
exp_continue
}
"already active" {
send_user "Already connected to VPN\r"
}
}
exit
I included an arrow to what I think is the source of the problem because when I run this script, it looks like expect_out contains Password:. Given what i read in man page I imagined that the buffer is cleared each time new match is made and therefore it contains string matched in most recent expect clause( in this case expect_user -re ":(.*)\r"), however it seems that I'm wrong. I did not see any notes saying that expect_user contents need to be accessed using different function like interact does, so I'm not sure where did my password go?
Thank you
When using send_user you will not see an echo of what you write, unlike when you send to the spawned command, so you will never match : unless the user explicitly types it. Also, you will read newline, not carriage-return. So use
expect_user -re "(.*)\n"
to get the user input and $expect_out(1,string) (1 not 0) will contain the input.
What you are observing is a timeout in your expect_user, which is why the variable is not updated and still contains the old match.
To protect yourself against unseen timeouts, you could set up a "global" timeout check by starting with
proc abort { } { send_user "Timeout!" ; exit 2 }
expect_before timeout abort
The expect_before is done "before" each expect, but also before each expect_user, it seems.

Non-reliable results from Expect when running a remote script

Because my company insists on using sudo I now have to scrap my ssh system I've built for running remote commands.
I have a script that will connect to a server, sudo into the db2 inst owner account and then run a script (previously delivered into /tmp). Maybe 1 in 10 times it will work (outputs 'Hello world' for my test).
Here is the ksh script on the remote server (/tmp/cwow/generic.ksh):
#!/usr/bin/ksh93
echo "Hello world"
[I've also tried adding sleep 5 and wait with mixed results but it doesn't solve the problem]
The expect script I'm running locally is:
#!/usr/bin/expect
set spath /tmp/cwow/generic.ksh
set pass $env(MYEXPECTPASS)
set user $env(MYEXPECTUSER)
if { [llength $argv] != 2 } {
send_user "USAGE: $argv0 host inst\n"
exit
}
set host [lindex $argv 0]
set inst [lindex $argv 1]
set timeout 10
log_user 1
exp_internal 0
eval spawn /usr/bin/ssh -t $user#$host "sudo su - cwow"
expect {
timeout { send_user "TimedOut"; exit }
-glob "assword:" {
send "$pass\r"
expect {
-glob "assword:" {
send "$pass\r"
expect {
-glob " " {
send "/tmp/cwow/generic.ksh\r\n"
expect {
-glob "world" {
send_user "Got it\r"
}
}
}
}
}
}
}
}
I should also note that I never get the 'Got it' message but I don't really need that to work, just curious why it doesn't. What I need to work reliably is for the script to run and, most of the time, it doesn't appear to.
Any ideas for a weak expect user would be greatly appreciated.
(not an answer, just a formatted comment)
You don't need to nest all the expect commands: if you expect a pattern with no action body, the script will continue to the next command. This is more readable, IMO:
expect {
timeout { send_user "TimedOut"; exit }
"assword:"
}
send "$pass\r"
expect "assword:"
send "$pass\r"
expect " "
send "/tmp/cwow/generic.ksh\r"
expect "world"
send_user "Got it\n"
Note, you should send to the spawned process with \r as "hitting Enter". But \n is used for send_user.

How to continue with next command if the remote switch is not responding in an expect script

I'm passing the switch name from a bash script. The bash script iterates through a text file and sends the switch name. Sometimes if the switch is not responding, the expect script gets stuck. I want the script to wait for some time and move to next switch or at least come out. I have used the timeout but with no help.
set timeout 60
.
.
.
expect "*> "
send "ssh -l admin $switchName.XXX.XXXXXX.net switchshow -portname > $filename1\r"
sleep 2
expect {
"*(yes/no)? " {
send "yes\r"
exp_continue
}
"*assword: " {
send "$password\r"
}
default {
exp_continue
}
}
Any suggestion is appreciated.
you want to expect to see the "timeout" keyword:
expect {
timeout {
puts "timed out waiting for $switchName"
}
"*(yes/no)? " {
send "yes\r"
exp_continue
}
"*assword: " {
send "$password\r"
exp_continue
}
eof
}
I also updated your conditions a bit so that you remain in the expect "loop" until the ssh command completes.

Expect: Prompt extending to next line

I am trying to install a software which is a shell script file. I am using expect to do the silent installation.
There is a strange line during the installation of the software where the prompt goes to the next line like this below.
ENTER AN ABSOLUTE PATH, OR PRESS <ENTER> TO ACCEPT THE DEFAULT
:
So I have tried with these 2 options, but it's NOT working!
1. expect " : " { send "/home/tester/IDir\r" }
2. expect "ENTER AN ABSOLUTE PATH, OR PRESS <ENTER> TO ACCEPT THE DEFAULT\
: " { send "/home/tester/IDir\r" }
Expect File
#!/usr/bin/expect -f
#Taking arguments from test.sh file
set File [lindex $argv 0]
set IDir [lindex $argv 1]
spawn sh /home/tester/$File
expect "PRESS <ENTER> TO CONTINUE: " { send "\n" }
exp_internal 1
expect -re "TO ACCEPT THE DEFAULT\r\n.*?:" { send "/home/tester/IDir\r" }
#expect " : " { send "/home/tester/IDir\r" }
expect "IS THIS CORRECT? (Y/N): " { send "Y\n" }
expect "PRESS <ENTER> TO CONTINUE: " { send "\n" }
sleep 2
expect "ENTER THE NUMBER OF THE DESIRED CHOICE: " { send "2\n" }
expect "PRESS <ENTER> TO EXIT THE INSTALLER: " { send "\n" }
sleep 2
expect eof
test.sh file
dir=James/SDD
cd /home/tester
file=`ls | grep xx_yy*_linux_x86-64.bin`
expect script.exp $file $dir
After analyzing the logs, realized that your expect code is getting timed out for most of the cases.
# This timeout is meant for the first expect word 'PRESS <ENTER> TO CONTINUE', so it is proceeding to the next expect word 'TO ACCEPT THE DEFAULT.*?:'
expect: timed out
Gate keeper glob pattern for 'TO ACCEPT THE DEFAULT
.*?: ' is 'TO ACCEPT THE DEFAULT
You need to add the proper timeout handling for the expect statement. Default timeout is 10 seconds. You can change it in a way as follows,
set timeout 120; # Now, timeout value is 2 min
I can see that you have used sleep in some places, in the same way, do that for earlier expect commands as well. (Using timeout would be better in such cases than sleeping.)
#Taking arguments from test.sh file
set File [lindex $argv 0]
set IDir [lindex $argv 1]
# A common handler for timeout.
# Customize it as per your need.
proc my_timeout_handler {} {
puts "Timeout happened :("
exit 1
}
spawn sh /home/tester/$File
set timeout 300; # 5 mins.
expect {
"PRESS <ENTER> TO CONTINUE: " { send "\n" }
timeout {my_timeout_handler}
}
expect {
-re "TO ACCEPT THE DEFAULT\r\n.*?:" { send "/home/tester/IDir\r" }
timeout {my_timeout_handler}
}
expect {
"IS THIS CORRECT? (Y/N): " { send "Y\r" }
timeout {my_timeout_handler}
}
expect {
"PRESS <ENTER> TO CONTINUE: " { send "\r" }
timeout {my_timeout_handler}
}
expect {
"ENTER THE NUMBER OF THE DESIRED CHOICE: " { send "2\r" }
timeout {my_timeout_handler}
}
expect {
"PRESS <ENTER> TO EXIT THE INSTALLER: " { send "\r" }
timeout {my_timeout_handler}
}
expect {
eof {puts "Program completed"}
timeout {my_timeout_handler}
}

Using conditional statements inside 'expect'

I need to automate logging into a TELNET session using expect, but I need to take care of multiple passwords for the same username.
Here's the flow I need to create:
Open TELNET session to an IP
Send user-name
Send password
Wrong password? Send the same user-name again, then a different password
Should have successfully logged-in at this point...
For what it's worth, here's what I've got so far:
#!/usr/bin/expect
spawn telnet 192.168.40.100
expect "login:"
send "spongebob\r"
expect "password:"
send "squarepants\r"
expect "login incorrect" {
expect "login:"
send "spongebob\r"
expect "password:"
send "rhombuspants\r"
}
expect "prompt\>" {
send_user "success!\r"
}
send "blah...blah...blah\r"
Needless to say this doesn't work, and nor does it look very pretty. From my adventures with Google expect seems to be something of a dark-art. Thanks in advance to anyone for assistance in the matter!
Have to recomment the Exploring Expect book for all expect programmers -- invaluable.
I've rewritten your code: (untested)
proc login {user pass} {
expect "login:"
send "$user\r"
expect "password:"
send "$pass\r"
}
set username spongebob
set passwords {squarepants rhombuspants}
set index 0
spawn telnet 192.168.40.100
login $username [lindex $passwords $index]
expect {
"login incorrect" {
send_user "failed with $username:[lindex $passwords $index]\n"
incr index
if {$index == [llength $passwords]} {
error "ran out of possible passwords"
}
login $username [lindex $passwords $index]
exp_continue
}
"prompt>"
}
send_user "success!\n"
# ...
exp_continue loops back to the beginning of the expect block -- it's like a "redo" statement.
Note that send_user ends with \n not \r
You don't have to escape the > character in your prompt: it's not special for Tcl.
With a bit of bashing I found a solution. Turns out that expect uses a TCL syntax that I'm not at all familiar with:
#!/usr/bin/expect
set pass(0) "squarepants"
set pass(1) "rhombuspants"
set pass(2) "trapezoidpants"
set count 0
set prompt "> "
spawn telnet 192.168.40.100
expect {
"$prompt" {
send_user "successfully logged in!\r"
}
"password:" {
send "$pass($count)\r"
exp_continue
}
"login incorrect" {
incr count
exp_continue
}
"username:" {
send "spongebob\r"
exp_continue
}
}
send "command1\r"
expect "$prompt"
send "command2\r"
expect "$prompt"
send "exit\r"
expect eof
exit
Hopefully this will be useful to others.
If you know the user ids and passwords, then you ought also to know which userid/password pairs are aligned with which systems. I think you'd be better off maintaining a map of which userid/password pair goes with which system then extracting that information and simply use the correct one.
So -- since you obviously don't like my advice, then I suggest you look at the wikipedia page and implement a procedure that returns 0 if successful and 1 if the expectation times out. That will allow you to detect when the password supplied failed -- the prompt expectation times out -- and retry. If this is helpful, you can remove your downvote now that I've edited it.
In retrospect, you'd probably want to do this in conjunction with the map anyway since you'd want to detect a failed login if the password was changed.

Resources