Create a new file every time a TCL script runs - tcl

I am new to TCL and got some stuff I need to automate and I need my code to log all the commands and results after the login process.
My main issue is that I need to create a distinct log file everytime I run the script and one way I found out was to "append" the unique "timestamp" to the file name.
Here is where it starts to get picky, you see, every time I try to append the variable "$time" to the filename it returns:
couldn't open "15-10-28/11:57:10--xxx.xxxx.xxxx.txt": no such file or directory
while executing
"log_file "$newfile" "
(file "ssh-test.tcl" line 31)
My code is as follows:
set user [lrange $argv 0 0]
set password [lrange $argv 1 1]
set ipaddr [lrange $argv 2 2]
set arg1 [lrange $argv 3 3]
set systemTime [clock seconds]
set time [clock format $systemTime -format %y-%m-%d/%H:%M:%S--]
set a "ssh"
set suffix ".txt"
append newfile "${a}${arg1}${suffix}"
set timeout -1
# now connect to remote UNIX box (ipaddr) with given script to execute
spawn ssh $user#$ipaddr
match_max 100000
# Look for passwod prompt
expect "*?assword:*"
# Send password aka $password
send -- "$password\r"
log_file "$newfile" ;
expect "*#"
send -- "\r"
send_user "This is the $argv0 Script"
send -- "scm $arg1\r"
expect "*#"
send -- "exit\r"
expect eof
If I use the 'set filename "${a}${arg1}${suffix}"' string and 'log_file "$filename"' it works just fine but it will append the new info to the already existing file and I want a new file everytime I run the script.
If I use the 'append newfile "${a}${arg1}${suffix}"' string and 'log_file "$newfile"' it won't work and return the error already referred.
Hope you guys can help me out and thanks in advance for any support.

You are creating the timestamp with / in it.
set time [clock format $systemTime -format %y-%m-%d/%H:%M:%S--]
While appending this to the variable newfile, it will become 15-10-28/11:57:10--xxx.xxxx.xxxx.txt.
Expect will think that there is a folder called 15-10-28 available and under which I have to create the file 11:57:10--xxx.xxxx.xxxx.txt. Since that folder is not available, you are getting the error message as no such file or directory

After figuring out that the date format was messing up my code, I started playing around with the special characters and got it working like this:
set time [clock format $systemTime -format %a_%b_%d_%Y#%H'%M'%S]
It is not the desired format but at least I got it working as I intended.

Related

Need help extracting specific lines from a changing logfile using expect

I'm trying to use an expect script to access a remote device via telnet, read/save the remote "EVENTLOG" locally, and then extract specific lines (serial numbers) from the log file. Problem is the log files are constantly changing so I need a way to search for specific strings. The remote device is Linux based, but doesn't have things like grep, vi, less, etc as it's QNX Neutrino, hence having to do it locally.
I've successfully gotten the telnet, read the file and save locally under control, but when I get to "reading" the file is when I have issues. Currently I'm just trying to get it to print what it found, but the script just exits without reporting anything except some extra braces??
#!/usr/bin/expect -f
set timeout -1
log_user 1
spawn telnet $IP
match_max 100000
expect "login:"
send -- "$USER\r"
expect "Password:"
send -- "$PW\r"
expect "# "
send -- "\r"
#at this point logged into device
#send command to generate the "dallaslog"
set dallaslog [open dallaslog.txt w]
expect "#"
send -- "cat `ls -rt /LOG/event*`\r"
expect "(cat) exited status=0"
set logout $expect_out(buffer)
puts $dallaslog "$logout"
close $dallaslog
unset expect_out(buffer)
set dallasread [open dallaslog.txt r]
set lines [split [read $dallasread] "\r"]
close $dallasread
puts "${green}$lines{$normal}"
#a debug line to print $dallasread in green so I can verify it works up to here
foreach line $lines {
if {[regexp {.*Dallas ID: 0.*\n} $lines match]} {
if {$match == 1} {
puts $line ;# Prints whole line which has 1 at end
}
}
}
expect "# "
send -- "exit\r"
interact
What I'm (eventually) looking for is the script to catch any line starting with "Dallas ID:" and then to save that information to a variable, so I can use the "scan" command to parse the line and extract information.
What I get is:
(the results from $lines being "puts" in green)
"...
<ENTRY TIME="01/01/1970 00:48:07" PROC="syncd" FILE="mips.cc" LINE="208" NUM="10000">
UTC step from 01/01/1970 00:48:08 to 01/01/1970 00:48:07
</ENTRY>
Process 3174431 (cat) exited status=0
}{}
# exit
Process 3162142 (sh) exited status=0.
Connection closed by foreign host."
Thank you in advance for all the help. I'm a newbie to TCL/expect (been toying with it since last July) but I'm finding it to be a pretty powerful tool, just hard for me to debug!
EDIT: Added more information per #meuh 's reponse.
Example: There can be up to 4 Dallas ID, but generally I only have 0 and 1. Goal is to get the SN, BC, CN for reach Dallas ID saved as variables to put in a separate text file.
<ENTRY TIME="01/01/1970 00:00:06" PROC="sys" FILE="PlatformUtils.cpp" LINE="1227" NUM="10044">
Dallas ID: 1 SN:00000622393A BC: J4AD945 CN: IS200BPPBH2BMD R0: 001C
</ENTRY>
The foreach loop I used was an example from an old question on stack overflow I tried to modify to use here, unsuccessfully.
EDIT: I should also probably mention that this event log is approximately 800 lines long every time it gets read, which is why I haven't posted an excerpt from it.
This regexp line is probably not doing what you want:
if {[regexp {.*Dallas ID: 0.*\n} $lines match]} {
if {$match == 1} {
puts $line
You are passing the list $lines instead of, presumably, the single line $line. The variable match will be set to the string that matched which must therefore include the words "Dallas" and so on, so it can never be 1.
Your code comment says Prints whole line which has 1 at end, but I'm not sure what you are looking for as you do not have any example data that fits the regexp.
If you choose your regexp pattern using grouping you could capture parts of the line so perhaps not need a further scan. Eg
regexp {PROC="([a-z]*)"} $line match submatch
would set variable submatch to syncd in your above example.
You may also have a fundamental problem caused by tcl's handling of \r\n on input from a file. The lines you got from $expect_out(buffer) do indeed have the 2 characters as end-of-line delimiters. However,
when using read, by default I believe, it will translate the same sequence to a normalised \n. So your split will not do anything, and you need to split on \n rather than \r. You can check the size of the list of lines you have with
puts [llength $lines]
If it is 1, then your split is not working. Replace it with
set lines [split [read $dallasread] "\n"]
This should help your loop, where for example you can try
foreach line $lines {
if {[regexp {.*Dallas ID: (\d+) SN:([^ ]+)} $line match idnum SN]} {
puts $line
puts "$idnum, $SN"
}
}
You must remove the \n at the end of your regexp, as this is no longer present after the split. I've extended the regexp example with (\d+) to match for the id number (\d matches a digit), and ([^ ]+) to match any number of non-space characters after the text SN:.
These values are captured by the use of () grouping, and are placed in the variables idnum and SN, which you should be able to see output by the second puts command.

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.

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)

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 save config file with date&time in a folder

I want to automatically back-up the start-up config for my routers, but it will overwrite the previous one. How can I save naming date&time to the back-up file to avoid overwriting.
Please help!
Command clock will help you with time-related stuff. As everything is a string, you can just insert the formatted date to the filename to be opened. w+ means opening file for writing and creating it if it didn't exist.
#!/usr/bin/tclsh
set f [open "[clock format [clock seconds] -format "%Y%m%d-%H%M%S"].txt" w+]
puts $f "foo"
close $f