EXPECT Script for Cisco / Juniper Config Backup - configuration

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)

Related

Different output when a tcl script is launched via tclsh and wish interpreters

I wrote a Tcl script to telnet a Linux server and perform some operations in it.
I send some command and expect * (expected everything to be captured in buffer).
Now I print the $expect_out(buffer) in a file.
Note: the command I send to the linux server invokes some sort of application in Linux (like a top command), but the output of that command is always the same if executed any number of times.
Double clicking the script file or wish script.tcl (using wish interpreter)
After completing the execution, I open the file and check for its contents. It has the contents of previous expect, i.e., a previous expect before expect * in the script, and some part of this expect (expect *). So the file content misses some text at the end.
Running the script from command prompt (using tclsh interpreter)
#> tclsh script.tcl
After completing the execution, I open the file and check for its contents. It has the contents of previous expect, i.e., a previous expect before expect * in script, and a all part of this expect (expect *).
In the above cases, if expect * is replaced with some text like -> expect "hello", then the script fails in wish interpreter and suceeds in tclsh interpreter.
Why is that? How can I fix it?
The script is as below
package require Expect
set DOWN2 \033OB
spawn telnet 172.19.10.10
after 30000
expect "login:"
send "admin\r"
expect "Password:"
after 1000
send "welcome123\r"
after 2000
expect {
timeout { exit 1 }
"MAIN"
}
expect ">"
after 3000
send "mgmt\r"
expect {
timeout { send_user "timed out, Bye "; exit 1 }
"Protocol"
}
#set fp [open textdoc.txt a+]
send "translation\r"
expect "prefix"
send "\r\r"
send "$DOWN2"
send "$DOWN2"
send "$DOWN2"
send "$DOWN2"
send "326"
send "\x16"
expect -re {limit No. : (\d+)}
set lim_val $expect_out(1,string)
set exp_out $expect_out(buffer)
set fp [open textdoc.txt a+]
puts $fp "total buffer is $exp_out"
close $fp
after 2000
exit
This code is successful with tclsh and fails for wish interpreter
fails at line >> set lim_val $expect_out(1,string)
But from the wireshark capture, I have found out that the text "limit No. : 2" is received even if run from wish.
If I try to print $expect_out(buffer) in file,
In tclsh, the contents stored in buffer is till the "limit No. : 2" is occurred
In wish, the contents stored in buffer ends even before the text "limit No. : 2" is occurred.

How to return back to previous script in expect

I am trying to reboot a system using expect. Here is my code:
#!/usr/bin/expect
set timeout 20
set name [lindex $arg0]
set user [lindex $arg1]
set password [lindex $arg2]
spawn telnet $name
expect "login:"
send "$user\n"
expect "Password:"
send "$password\n"
expect "<system prompt>$"
send "<some command>\n"
expect "<system prompt>$"
send "./my_script\n" --> code is below
send "<back to the telnet system prompt and running a builtin command>"
expect "g>"
send "some built in command"
Here is the code for my_script (I am rebooting a system here after which the connection gets lost and the above script (main script) ends at line )
#!/usr/bin/expect
set name [lindex <some ip>]
set user [lindex <name>]
set password [lindex <password>]
spawn telnet $name
expect "Login:"
send "$user\n"
expect "Password:"
send "$password\n"
expect "<system prompt>"
send "reboot\n"
expect "Are you sure you want to reboot (y/n)?"
send "y"
Here it lost connection and never returns to the main script and continue with the rest of the script.
NOTE: it's working if i put interact at the end of both the scripts but I don't want to put an interact as we don't want anything from user.
Your inner script probably ought to do:
expect eof
close
exit
at the end. However, more generally you should add:
exp_internal 1
to your script (usually just before the spawn) when debugging it so that you can figure out what's going wrong. Otherwise you could be hitting unexpected problems and not know it.

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.

Using Expect, how do I extract certain value from a file and input into a variable of an Expect script

I have two files. One is called bgp_ip.log and the other is an expect script called bgp.sh
bgp_ip.log contains just one IP address which was input into the file by another script called getip.sh which is working fine.
Now I'm trying to figure out how to get bgp.sh to extract the IP address in bgp_ip.log and place it into a variable container. In this script context, the variable container is called $BGP_IP as shown in the script below:
The reason for having this variable so that the script below is able to execute the Router BGP commands based on the IP address obtained from bgp_ip.log
#!/usr/bin/expect
set username "testaccount"
set password "testaccount"
set hostname "R-KANSAS-01"
set BGP_IP "?????" <<<< I'm not sure how can I extract the IP from bgp_ip.log and place into this variable
spawn telnet "$hostname"
expect "login: " {
send "$username\n"
expect "Password: "
send "$password\n"
expect "> "
send "show bgp summary instance $BGP_IP\n"
log_file "R-KANSAN-01_temp.log"
log_user 1
expect "#"
send "exit\n"; exit 0
interact
}
Any help is appreciated..Thanks.
You need to read the file and get the data from it
set file_name "bgp_ip.log"
set fp [open $file_name "r"]
set BGP_IP [read $fp]
close $fp
The variable BGP_IP will hold the IP address. I am assuming that the file contains only the IP address and nothing else.
If not, then you need regexp to get it.
Just add this code before spawning telnet in your code.

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!