Expect programming:How to expect a particular string - tcl

I have logged in the CUCM then we are trying to execute one command which is coming correct, but by the Expected value we are not able to execute the second command.
first we are doing like this:-
send -i $install_id "utils ctl set-cluster mixed-mode\r\n"
puts "we are hitting yes &&&&&&&&&&"
expect {
-i $install_id -re ".*" {
send -i $install_id "y\r"
puts "$$$$$$$$$$$$$$$ we are inside...."
}
}
puts "we are done %%%%%%%%%%"
return 1
here first command send -i $install_id "utils ctl set-cluster mixed-mode\r\n" is getting executed sucessfully , but it is not going inside the expect loop.
the output of first command is something like:-
admin:utils ctl set-cluster mixed-mode
This operation will set the cluster to Mixed mode. Do you want to continue? (y/n):
after this the cursor will be in next line where i have to give y and enter, the output statment inside expect is also not getting printed

I'd be doing something like:
expect {
-i $install_id
-ex "continue? (y/n):" {
puts "I FOUND A CONTINUE PROMPT"
send -i $install_id "y\r"
exp_continue
}
"$thePrompt" {
puts "I FOUND A SYSTEM PROMPT"
}
}
Like that, it would only stop that expect once the command has finished, but it will respond to the continue prompt and keep going (because exp_continue). In this case, the -ex option is suitable as that matches a literal string, a great convenience when the thing that you are looking for contains multiple regular expression metacharacters (?, ( and )).
And change "$thePrompt" for the right thing to actually match the prompt, of course.

Use autoinspect(1).
Start it, then run all your commands manually, inspect the script.exp file it created. It should have recorded the exact matched strings, in case there is some weird shell coloring going on which makes you missing your expected string but having no idea why.

Related

Proper Error handling EXPECT / TCL

What is the best way to prevent sending commands to a dead process?
Sometimes my session gets terminated when it's supposed to be open so I end up sending commands and getting the error:
send: spawn id exp4 not open
I was trying to do something like
if [catch send "test\r"] {
puts "send error!"
}
but it seems like the query is true every pass.
that's the simplest example, but I have more complex "send / expects" where I use capture groups etc, so putting a catch around every "send / expect" or creating a function doesn't seem that useful.
can you wrap a catch around the entire program? What is the proper way to catch errors like these?
There's a FAQ written by the Expect author that addresses this: http://expect.sourceforge.net/FAQ.html#q64
Seems like you want something like
expect_before {
eof { respawning_the_process }
}
I'm sure there's some wrinkles to be ironed out (like what to do when the process is supposed to end)
The problem with this:
if [catch send "test\r"] {
is two-fold:
you did not put braces around the condition, so it's not getting evaluated at the right time.
you did not provide the right arguments to the catch command
You would want to write:
if {[catch {send "test\r"} output] != 0} {
This can be abstracted into a proc
proc send {args} {
set status [catch [list exp_send {*}$args] output]
# error handling if $status is non-zero
}
"exp_send" is a builtin alias for the expect "send" command, so it's safe to override "send" with a proc, minimizing the amount of code changes you need.

Expect: exit/finish/close interact on returned output

I need to use expect in order to navigate a program menu and then allow user input. After the user finishes the input, I need to tell expect to take away user control if a specific string is returned by that program.
expect -c '
[login etc, navigate to the desired option]
expect "[string]"; interact
[user input] -> until here everything works
expect "[specific string returned by the PROGRAM, NOT user input]"
[take away control from the user / exit interact if the above specific string is returned] -> this doesn't work
expect "string"; send "[exit command]\r"' -> what should happen after
Also, I need to trap all possible signals because if one of them is used, the user can end up at shell cli with root access.
I've been trying to find an answer on this for hours but my attempts on producing a valid syntax within expect ended up in frustration as the documentation did not help me at all in this direction.
Here's an example for you. Expect will be controlling this shell script:
#!/bin/sh
PS3="your choice? "
select answer in foo bar baz quit; do
case $answer in
quit) echo "bye bye"; break;;
*) echo "you chose $answer";;
esac
done
echo "out of select loop: hit enter..."
read x
echo "exiting ..."
The expect program is:
#!/usr/bin/env expect
spawn sh test.sh
set keyword "out of select loop"
# signal handlers: ignore these (can't trap SIGKILL)
trap SIG_IGN {HUP INT TERM QUIT}
# interact until a certain pattern is seen
# in the output (-o) of the process
interact {
\003 {
# prevent the user sending SIGINT to the spawned process
puts "don't press Ctrl-C again"
}
-o $keyword {
send_user "you quit\n"
send_user $keyword
return # "return" exits the interact command
}
}
# do other stuff here, for example, hit enter to allow the
# shell script to terminate
expect -re {\.\.\.\s*$}
send_user "... hitting enter ...\n"
send -- \r
expect eof
The expect man page is rough going. You'll be much happier with the Exploring Expect book.

EXPECT Script for Cisco / Juniper Config Backup

Firstly I am asking this as a beginner in scripting, currently I have an expect script which automatically logs in to predefined Cisco devices and runs certain commands, I would like to update my script so that this same script can also backup Juniper devices.
Ideally what I would like the script to do is (in pseudo code)
login / send credentials
expect prompt
send command "show version"
if output contains "JUNOS" then
send command 1
send command 2
send command 3
otherwise if output contains "Cisco" then
send command 1
send command 2
send command 3
Im sorry if this has been asked before, but I have tried searching and searching and couldn't find an answer if anyone can assist with this I would really appreciate it. I have also included my current expect script for your reference (this script gets called by a BASH Script)
set timeout 5
set hostname [lindex $argv 0]
set username "user"
set password "pass"
spawn ssh $username#$hostname
expect "Password:"
send "$password\n"
expect "#" {
send "terminal length 0\n"
send "show running-config\n"
expect "end\r"
send "\n"
send "exit\n"
}
---- UPDATE ---
Thanks for your input Dinesh - I have updated my script to include what you provided (as below)
set timeout 5
set hostname [lindex $argv 0]
set username "user"
set password "pass"
set prompt "(>|#|\\\$) $"
spawn ssh $username#$hostname
expect "*assword:"
send $password\r
send \r
expect -re $prompt {
send "show version\r"
expect -re $prompt
expect *;
set output $expect_out(buffer);
#Basic string check logic with Tcl
if { [string first "JUNOS" $output ]!=-1 } {
send "show configuration | display set | no-more"
expect -re $prompt
send "exit\r"
} else {
send "terminal length 0\r"
expect -re $prompt
send "show run\r"
expect "end"
send \r
expect -re $prompt
send "exit\r"
}
}
However when i run this script the issue I have is that the output of the "show version" doesn't seem to be matching my "string check" and the script therefore ignores the "if" statement and proceeds with the "else" statement.
The output of the "show version" command is below - what will I need to modify so that the "JUNOS" string gets matched?
user#host> show version
Hostname: host
Model: srx240h
JUNOS Software Release [11.4R7.5]
--- EDIT 2: Output from the script
05c4rw#testpc:~/script$ ./ssh.sh
spawn ssh user#juniperhost
## LOGIN BANNER - Removed for brevity
user#juniperhost's password:
--- JUNOS 11.4R7.5 built 2013-03-01 11:40:03 UTC
user#juniperhost> show version
Hostname: juniperhost
Model: srx240h
JUNOS Software Release [11.4R7.5]
user#juniperhost> show configuration | display set | no-more
set version 11.4R7.5
## *** OUTPUT REMOVED FOR BREVITY / PRIVACY ***
## *** END OF OUTPUT from previous command
user#juniper> spawn ssh user#ciscohost
password:
## LOGIN BANNER - removed for brevitiy
ciscohost#05c4rw#testpc:~/script$
set timeout 5
set hostname [lindex $argv 0]
set username "user"
set password "pass"
spawn ssh $username#$hostname
expect "Password:"
send "$password\r"
expect "#" {
send "terminal length 0\r"
expect "#"
# This is to clean up the previous expect_out(buffer) content
# So that, we can get the exact output what we need.
expect *;
send "show running-config\r"
expect "end"
#Now, the content of 'expect_out(buffer)' has the whole 'show run' output
set output $expect_out(buffer);
#Basic string check logic with Tcl
if { [string first "JUNOS" $output ]!=-1 } {
# Apply your logic here
# send "command1"
# expect "#"
} else {
# Same as above
# I assume, there are 2 possibilities. So, keeping 'else' part alone.
# Have 'else if', if you have more than 2.
}
}
Notice that each line sent by the script is terminated with \r. This denotes a return character and is exactly what you would press if you entered these lines at the shell, so that is exactly what Expect has to send. It is a common mistake to terminate send commands to a process followed by \n. In this context, \n denotes a linefeed character. You do not interactively end lines with a linefeed. So Expect must not either. So, always use \r.
You can have a look at here if you are interested to know more about the why you need expect *. (which is a separate story)
I can see that there are some commands used only with send in your example. Basically, expect will work with two feasible commands such as send and expect. In this case, if send is used, then it is mandatory to have expect (in most of the cases) afterwards. (while the vice-versa is not required to be mandatory)
This is because without that we will be missing out what is happening in the spawned process as expect will assume that you simply need to send one string value and not expecting anything else from the session.
This might not be what you were looking for but thought I will post it just in case.
You might want to look into something like Rancid instead of having those scripts. Rancid will not only backup your device configs, it will also provide you with a diff on the devices it is managing at pre-defined intervals (for e.g if you set the interval to 15 mins, rancid will login to your devices every 15 mins grab the configs, back them up and do a diff with the previous version and show you the diff)

How to store output in a variable while using expect 'send' command

Thanks.
But the account and password are needed. So I must send them and then send ovs-vsctl command.
the scripts are something like this:
spawn telnet#ip
expect -re "*login*" {
send "root"
}
expect -re "password*" {
send "****"
}
send "ovs-vsctl *******"
I want to store the output of this command send "ovs-vsctl ****", but many times I got some output of the command "send "password"", how can I get the output of send "ovs-vsctl****". The output of the command send "ovs-vsctl *** are two string and each string occupies one line.
Thanks.
Maybe:
log_user 0 ;# turn off the usual output
spawn telnet#ip
expect -re "*login*"
send "root\r"
expect -re "password*"
send "****\r"
send "ovs-vsctl *******"
expect eof
puts $expect_out(buffer) ;# print the results of the command
Expect works with an input buffer, which contains everything that is returned from the interactive application, meaning both process output and your input (as long is it is echoed from the remote device, which is usually the case).
The expect command is used to recover text from the input buffer. Each time a match is found, the buffer up to the end of that match is cleared, and saved to $expect_out(buffer). The actual match is saved to $expect_out(0,string). The buffer then resets.
What you need to do in your case is match the output with an expect statement, to get what you want.
What I would do in your case is match the remote device prompt after sending the password, then match it again after command was sent. That way the buffer after the last match will hold the needed output.
Something along the lines of:
[...]
expect -re "password*" {
send "****"
}
expect -re ">"
send "ovs-vsctl *******\r"
expect -re ">" # Better if you can use a regexp based on your knowledge of device output here - see below
puts $expect_out(buffer)
By matching using a regexp based on your knowledge of the output, you should be able to extract only the command output and not the echoed command itself. Or you can always do that after-the-fact, by using the regexp command.
Hope that helps!

How do you save and parse a command output in Expect?

I am half-way through writing an Expect script on a Linux server which is supposed to telnet to a router in order to collect some system information. So far my script can successfully make the connection, run a router command, disconnect and terminate.
The command displays a few lines which I need to parse, something I am not sure how to do in Expect. How can I save the output, grep a line, then a column from the line, and finally save the result in a file? If possible, I would like to use Expect entirely rather than a work-around (for example Expect embdded in Bash).
Thanks for your time.
jk04
Two tips for expect development:
autoexpect to lay out a framework for your automation
exp_internal 1 to show verbosely what expect is doing internally. This one is indispensable when you can't figure out why your regular expression isn't capturing what you expect.
basically, $expect_out(buffer) [1]. holds the output from last expect match to the current one. you can find your command output there.
and for the string manipulation, you can simply employ the tcl's built-in [2][3].
"How to access the result of a remote command in Expect" http://wiki.tcl.tk/2958
"regexp" http://wiki.tcl.tk/986
"string match" http://wiki.tcl.tk/4385
I've faced and solved a similar problem for interacting with bash. I believe the approach generalizes to any other interactive environment that provides no-op, fixed-string output.
Basically, I wrap the command with two fixed strings and then search for the pattern that includes these strings at the beginning and end, and save the content in between them. For example:
set var "";
expect $prompt { send "echo PSTART; $command; echo PEND;\r"; }
expect {
-re PSTART\r\n(.*)PEND\r\n$prompt { set var [ string trim $expect_out(1,string) ]; send "\r"; }
-re $prompt { set var "" ; send "\r"; }
timeout { send_user "TIMEOUT\n"; exit }
}
I suspect that this approach would work with a shell's comment characters as well, or a simple status command that returns a known output.
Then you can do whatever you need with the content of 'var'.