expect script's puts is printing on wrong position - tcl

I am using below expect script on a linux machine, to run command(s) on a remote device via ssh, and printing its output back on the linux machine where script ran.
#!/usr/bin/expect -f
set fp [open "~/passwfile" r]
set data [read $fp]
set host [lindex $argv 0];
set prompt [lindex $argv 1];
set timeout 10
spawn ssh $host
while {1} {
expect \
{
"*(yes/no)? " {send "yes\r"}
"*assword:" {send "$data\r"}
"*$host*$prompt" {break}
timeout {exit 2}
eof {puts "\rBreaking - EOF\r"; exit 1}
}
}
for {set i 2} {$i <= [llength $argv]} {incr i 1} {
if {$i < [llength $argv]} {puts "";puts ""; puts "\r## command [expr {$i - 1}] start ##"}
send "\r"
expect \
{
"*$host*$prompt" {send "[lindex $argv $i]\r"}
timeout {exit 4}
eof {puts "\rBreaking - EOF\r"; exit 3}
}
while {1} {
expect \
{
"*$host*$prompt" {break}
"Press <SPACE> to continue or <Q> to quit:" {send " "}
"(more" {send " "}
"More-" {send " "}
"ESC->exit" {send "\x1b\r"}
timeout {exit 4}
eof {puts "\rBreaking - EOF\r"; exit 3}
}
}
if {$i < [llength $argv]} {puts "\r## command [expr {$i - 1}] finish ##"}
}
send "exit\r"
puts ""
exit 0
It takes hostname, prompt character and command(s), as parameters.
Example
$ ./expectssh bcr03a.dal10 "#" "sh int et1/8"
I need each command's output to be printed between its own start and finish lines. For above example this is the expected output:
.
.
.
bcr03a.dal10#
## command 1 start ##
bcr03a.dal10# sh int et1/8
Ethernet1/8 is up
admin state is up, Dedicated Interface
Belongs to Po106
Hardware: 40000/100000 Ethernet, address: 70d3.7962.898c (bia 70d3.7962.898c)
MTU 9216 bytes, BW 100000000 Kbit, DLY 10 usec
reliability 255/255, txload 1/255, rxload 1/255
Encapsulation ARPA, medium is broadcast
Port mode is trunk
full-duplex, 100 Gb/s, media type is 100G
Beacon is turned off
Auto-Negotiation is turned off
Input flow-control is off, output flow-control is off
Auto-mdix is turned off
Rate mode is dedicated
Switchport monitor is off
EtherType is 0x8100
EEE (efficient-ethernet) : n/a
admin fec state is auto, oper fec state is cl91
Last link flapped 59week(s) 4day(s)
Last clearing of "show interface" counters never
2 interface resets
Load-Interval #1: 30 seconds
30 seconds input rate 249767432 bits/sec, 25786 packets/sec
30 seconds output rate 178732832 bits/sec, 25343 packets/sec
input rate 249.77 Mbps, 25.79 Kpps; output rate 178.73 Mbps, 25.34 Kpps
Load-Interval #2: 5 minute (300 seconds)
300 seconds input rate 335318184 bits/sec, 24583 packets/sec
300 seconds output rate 197360256 bits/sec, 25510 packets/sec
input rate 335.32 Mbps, 24.58 Kpps; output rate 197.36 Mbps, 25.51 Kpps
RX
455840060118 unicast packets 436446550 multicast packets 87396954 broadcast packets
456363082369 input packets 585827288945618 bytes
37037870002 jumbo packets 0 storm suppression packets
0 runts 0 giants 0 CRC/FCS 0 no buffer
0 input error 0 short frame 0 overrun 0 underrun 0 ignored
0 watchdog 0 bad etype drop 0 bad proto drop 0 if down drop
0 input with dribble 0 input discard
0 Rx pause
TX
431362512003 unicast packets 2143469193 multicast packets 555138516 broadcast packets
434061134995 output packets 489379806337887 bytes
19176210395 jumbo packets
0 output error 0 collision 0 deferred 0 late collision
0 lost carrier 0 no carrier 0 babble 0 output discard
0 Tx pause
bcr03a.dal10#
## command 1 finish ##
Interesting part is I am receiving this expected output sometimes, but some other times the "## command 1 finish ##" line is being printed earlier. Usually 2 or 3 lines above the "RX" line, on above expected output snippet.
Can you please help me to fix this random behavior?

Related

cmdline argument parsing using tcl?

I am trying to pass the parameters to Spirent test center tool using command line arguments, where I am passing slots, ports, frame size and load. I want to store the Slots and ports in array, where number of ports are dynamic.
I tried simple code with cmdline which can handle fixed ports
package require cmdline
set parameters {
{s.arg "" "Slot"}
{p.arg "" "Port"}
{l.arg "100" "Load"}
{f.arg "256" "Framesize"}
{debug "Turn on debugging, default=off"}
}
#set option(l) 100
set usage "- A simple script to demo cmdline parsing"
if {[catch {array set options [cmdline::getoptions ::argv $parameters $usage]}]} {
puts [cmdline::usage $parameters $usage]
} else {
parray options
}
#puts [array get options]
puts $options(l)
puts $options(f)
script Output:
C:\Tcl\bin>tclsh opt.tcl -s 1 -f 128
options(debug) = 0
options(f) = 128
options(l) = 100
options(p) =
options(s) = 1
100
128
Here I would like to pass all the ports for each slots onetime ,
tclsh opt.tcl -s 1 2 -p 11 12 13 14 -f 256 -l 100
Where slots are 1 and 2 and ports in each slot are 11,12,13,14 and need to create array of slot and ports. Could you please suggest some method to achieve this.
Try
tclsh opt.tcl -s "1 2" -p "11 12 13 14" -f 256 -l 100
It works for me under Windows 10, at least. The thing is that the lists of slots and ports need to be one value each: the quotes ensure that.
I tried the following method with some corrections:
set arglen [llength $argv]
while {$index < $arglen} {
set arg [lindex $argv $index]
#puts $arg
switch -exact -- $arg {
-s {
set args($arg) [lindex $argv [incr index]]
set slot($y) $args($arg)
incr y
}
-p {
set args($arg) [lindex $argv [incr index]]
set port($z) $args($arg)
incr z
}
-l {
set args($arg) [lindex $argv [incr index]]
global Load
set Load $args($arg)
}
-f {
set args($arg) [lindex $argv [incr index]]
set frameLength $args($arg)
}
}
incr index
}
Command to run:
C:\Tcl\bin>tclsh l1.tcl -s 1 -p 11 -p 12 -l 10 -f 1

TCL proc and byte code compile - what is the link?

Several times I run into mentioning that it is best to put script into proc in order to boost run time performance, e.g. this answer has the following:
That is one reason for the advices to put all your code inside procedures (they get byte-compiled that way)
Something does not click in me.
Just as described in the answer, the first time a script runs, there is a check if a command can be byte-code compiled, if it is, then it is compiled. This makes total sense. But I do not see how "proc" plays an important role. E.g. compare the following 2 scripts:
set v [concat [lindex $::argv 1] [lindex $::argv 2]]
myCmd $v
and
proc p1 {v1 v2} {
set v [concat $v1 $v2]
return [myCmd $v]
}
p1 [lindex $::argv 1] [lindex $::argv 2]
My high level interpretation of the 2 scripts tells the following:
In running either script the first time, "set", "concat", "lindex" and "return" commands are compiled
The second script also has "proc" compiled.
"myCmd" is not compiled in either script
Subsequent running of either script runs the bycode except "myCmd".
So what is the advantage of "proc"?
I did run dissamble on the scripts:
The first script:
ByteCode 0x0x83fc70, refCt 1, epoch 3, interp 0x0x81d680 (epoch 3)
Source "set v [concat [lindex $::argv 1] [lindex $::argv 2]]\nmy"
Cmds 5, src 61, inst 50, litObjs 4, aux 0, stkDepth 4, code/src 0.00
Commands 5:
1: pc 0-41, src 0-51 2: pc 2-39, src 7-50
3: pc 4-20, src 15-30 4: pc 21-37, src 34-49
5: pc 42-48, src 53-60
Command 1: "set v [concat [lindex $::argv 1] [lindex $::argv 2]]"
(0) push1 0 # "v"
Command 2: "concat [lindex $::argv 1] [lindex $::argv 2]"
(2) push1 1 # "concat"
Command 3: "lindex $::argv 1"
(4) startCommand +17 1 # next cmd at pc 21
(13) push1 2 # "::argv"
(15) loadScalarStk
(16) listIndexImm 1
Command 4: "lindex $::argv 2"
(21) startCommand +17 1 # next cmd at pc 38
(30) push1 2 # "::argv"
(32) loadScalarStk
(33) listIndexImm 2
(38) invokeStk1 3
(40) storeScalarStk
(41) pop
Command 5: "myCmd $v"
(42) push1 3 # "myCmd"
(44) push1 0 # "v"
(46) loadScalarStk
(47) invokeStk1 2
(49) done
The second script:
ByteCode 0x0xc06c80, refCt 1, epoch 3, interp 0x0xbe4680 (epoch 3)
Source "proc p1 {v1 v2} {\n set v [concat $v1 $v2]\n return"
Cmds 4, src 109, inst 50, litObjs 5, aux 0, stkDepth 4, code/src 0.00
Commands 4:
1: pc 0-10, src 0-67 2: pc 11-48, src 69-108
3: pc 13-29, src 73-88 4: pc 30-46, src 92-107
Command 1: "proc p1 {v1 v2} {\n set v [concat $v1 $v2]\n return"
(0) push1 0 # "proc"
(2) push1 1 # "p1"
(4) push1 2 # "v1 v2"
(6) push1 3 # "\n set v [concat $v1 $v2]\n return ["
(8) invokeStk1 4
(10) pop
Command 2: "p1 [lindex $::argv 1] [lindex $::argv 2]"
(11) push1 1 # "p1"
Command 3: "lindex $::argv 1"
(13) startCommand +17 1 # next cmd at pc 30
(22) push1 4 # "::argv"
(24) loadScalarStk
(25) listIndexImm 1
Command 4: "lindex $::argv 2"
(30) startCommand +17 1 # next cmd at pc 47
(39) push1 4 # "::argv"
(41) loadScalarStk
(42) listIndexImm 2
(47) invokeStk1 3
(49) done
So script 2 does have 1 less TCL command, but both scripts have 49 byte code commands.
Finally the running test, I comment out "myCmd" because I actually do not have such extension. Here is the result:
% time {source 1.tcl} 10000
242.8156 microseconds per iteration
% time {source 2.tcl} 10000
257.9389 microseconds per iteration
So the proc version is even slower.
What do I miss? Or rather, what is the exact understanding of proc and performance?
The really big reason that putting things in a procedure matters is that procedures have a local variable table. Variables in the LVT can be accessed by numerical index, which is stupendously faster than the alternative (a lookup via a hash table, even though Tcl's got an extremely fast hash table implementation). It doesn't make much difference for a one-off call, but with repeated calls or a loop, the performance differences rapidly add up to something significant. This can quite easily make the extra cost of the extra compilation and stack frame management (procedures aren't free to enter, though we try to keep them cheap) basically irrelevant in real scripts.
And yes, Tcl actually bytecode-compiles everything. It's just that it often generates sub-optimal bytecode outside of procedure(-like context)s; in the limit case for suboptimality, all the bytecode is doing is assembling arguments into a list, doing a dynamic command invoke, and routing the result.
(It's important when reading Tcl's disassembled bytecode to remember that the costs of particular bytecodes are not all the same. You cannot just count the number of instructions to work out the cost in any useful way. For example, push1 is very cheap but invokeStk1 is potentially very costly. Another example, loadScalarStk is usually much more expensive than loadScalar1; the latter is used inside procedures only.)
The following two scripts demonstrate the performance gain due to usage of procs. In the second script the internal loop is extracted into a proc, leading to a 5x speedup.
without_proc.tcl
#!/usr/bin/env tclsh
set sum 0
set n 10000
set k 100
for { set i 0 } { $i < $k } { incr i } {
set s 0
for { set j 0 } { $j < $n } { incr j } {
set s [expr {$s + $j}]
}
set sum [expr {$sum + $s}]
}
puts "sum=$sum"
with_proc.tcl
#!/usr/bin/env tclsh
proc foo {n} {
set s 0
for { set j 0 } { $j < $n } { incr j } {
set s [expr {$s + $j}]
}
return $s
}
set sum 0
set n 10000
set k 100
for { set i 0 } { $i < $k } { incr i } {
set s [foo $n]
set sum [expr {$sum + $s}]
}
puts "sum=$sum"
Benchmark:
$ tclsh
% time {source with_proc.tcl} 1
sum=4999500000
67482 microseconds per iteration
% time {source without_proc.tcl} 1
sum=4999500000
406557 microseconds per iteration
or
$ time tclsh with_proc.tcl
sum=4999500000
real 0m0.089s
user 0m0.080s
sys 0m0.004s
$ time tclsh without_proc.tcl
sum=4999500000
real 0m0.401s
user 0m0.388s
sys 0m0.016s

[Expect]spawn id exp7 not open

I am trying to get my expect script to read a file, run a command for every line of the file and exit saving the log. The code is below:
#!/usr/bin/expect -f
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 %a_%b_%d_%Y#%H'%M'%S]
set fp [open "$arg1" r]
set a "ssh-"
set b ".txt"
set s "_"
append newfile "${a}${arg1}${s}${time}${b}"
set timeout -1
spawn ssh $user#$ipaddr
match_max 100000
expect "*?assword:*"
send -- "$password\r"
log_file "$newfile" ;
expect "*#"
send_user "This is the $argv0 Script\r"
while {[gets $fp line] != -1} {
send -- "scm $line\r"
expect "*#"
}
close
send -- "exit\r"
expect eof
My problem is that once it comes to the end of the file i get the following error:
E6000_Lab_1# send: spawn id exp7 not open
while executing
"send -- "exit\r""
(file "filetest.tcl" line 28)
Can anyone help me get rid of this error please?
Sorry for doing this but it seems that I got the asnwer myself once again.
Thanks very much for all of you who answer and provide some ideas to the resolution of these issues.
The solution to my problem was on the id of the file that had been opened. Once I closed that, my code stopped crashing, the snipet is below:
while {[gets $fp line] != -1} {
send -- "scm $line\r"
expect "*#"
}
close $fp
send "ping xxx.xxx.xxx.xxx timeout 1 repeat-count 100\r"
expect "# "
send -- "exit\r"
expect eof
As you can see, the "$fp" after the "close" argument allows me to send the next command out of the loop and without errors.
You can't do either send or expect after you close the connection to the subprocess.

No Such Variable When Using While Loop in Expect

I'm attempting to access a variable within a while loop in expect, but I keep getting an error that the variable doesn't exist. The code in question:
#!/usr/bin/expect
spawn ./simulator.sh
set timeout 1
...<extra code>...
# Return the time in seconds from epoch
proc getTime {year month day hour minute} {
set rfc_form ${year}-${month}-${day}T${hour}:${minute}
set time [clock scan $rfc_form]
return $time
}
# Get a handle to the file to store the log output
set fileId [open $filename "a"]
# Get one line at a time
expect -re {.*the number of lines from the debug log file to appear on one page.*}
send "1\r"
# Get the initial time stamp and store the first line
expect -re {^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})\..*$} {
puts -nonewline $fileId $expect_out(0,string)
set initTime $expect_out(1,string) $expect_out(2,string) $expect_out(3,string) $expect_out(4,string) $expect_out(5,string)
}
send "1\r"
# Iterate over the logs until we get at least 5 minutes worth of log data
while { true } {
expect -re {^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})\..*$} {
puts -nonewline $fileId $expect_out(0,string)
set currentTime $expect_out(1,string) $expect_out(2,string) $expect_out(3,string) $expect_out(4,string) $expect_out(5,string)
}
# 300 = 5 minutes * 60 seconds
if { $initTime - $currentTime > 300 } break
expect -re {.*enter.*}
send "n\r"
}
...<extra code>...
And the error I get is:
$ ./test.sh
the number of lines from the debug log file to appear on one page1
201505151624.00 721660706 ns | :OCA (027):MAIN (00) | CONTROL |START LINE
enter1
201505151620.00 022625203 ns | :OCA (027):MAIN (00) | CONTROL |com.citrix.cg.task.handlers.ADDeltaSyncHandler:ThreadID:1182, Delta sync on:activedirectory2
entercan't read "initTime": no such variable
while executing
"if { $initTime - $currentTime > 300 } break"
("while" body line 7)
invoked from within
"while { true } {
expect -re {^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})\..*$} {
puts -nonewline $fileId $expect_out(0,string)
set currentTime $expect_o..."
(file "./test.sh" line 42)
I'm sure I'm doing something incredibly stupid, but I can't figure it out for the life of me. Thanks for the help in advance!
This expect pattern has not matched:
expect -re {^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})\..*$} {
puts -nonewline $fileId $expect_out(0,string)
set initTime $expect_out(1,string) $expect_out(2,string) $expect_out(3,string) $expect_out(4,string) $expect_out(5,string)
}
You would know if it did match because you would get a syntax error: the set command takes only one or two arguments, and you have given it 6.
Add exp_internal 1 to a line before the spawn command, and expect will show you verbose debugging to let you know how your patterns are or are not matching.
To solve the set syntax error, you probably want
set initTime [getTime $expect_out(1,string) $expect_out(2,string) $expect_out(3,string) $expect_out(4,string) $expect_out(5,string)]
Also, in the if condition, be aware that $initTime will be smaller than $currentTime , so {$initTime - $currentTime > 300} will never be true, so your loop will be infinite.

spawning a router stops when router reloads, any way is there to automatically re-spawn after router is up reloaded

i run a tcl send expect script in a file and execute as ./file
#!/usr/bin/expect
spawn -noecho telnet 42.0.1.11
set timeout 900
expect "login:"
send "admin\r"
expect "Password: "
send "ram\r"
expect "#"
for {set i 0} {$i <= 1000000000} {incr i} {
some router commands
}
this works fine until router reloads, when router reloads, this script stops as spawn id not open., i want to resume the script (i dont know exactly how much time it takes to reload as it varies most of time).,is there any way to resume the script automatically
Thanks
Wrap your login procedure into a proc and call it again if you get EOF. Here is the basic idea, just remember that there may be other ways for things to go wrong.
Edit: I have rewritten the code after some investigation. This has been tested on Linux with "router reboot" being simulated by Solaris 10 workstation (sorry no Cisco to reboot here). As I mentioned ping implementations differ from OS to OS so proc Ping may need to be changed to accommodate your situation.
#!/usr/bin/tclsh
package require Expect
proc RouterLogin {ip user pass} {
spawn -noecho telnet $ip
set timeout 60
expect "login:"
send "$user\r"
expect "Password: "
send "$pass\r"
expect "#"
return $spawn_id
}
proc RouterPing ip {
# ping retry limit - 3
# delay between retries - 30 seconds
set limit 3
set delay 30
set attempt 1
set result false
while {$result == false && $attempt <= $limit} {
set result [Ping $ip]
incr attempt
if {!$result && $attempt <= $limit} {
# wait $delay seconds
after [expr {1000 * $delay}]
}
}
return $result
}
proc Ping ip {
set pingCmd "ping -c 1 $ip"
catch {eval exec $pingCmd} pingRes
if {[regexp "bytes from" $pingRes]} {
return true
} else {
return false
}
}
proc RouterExec {ip user pass commandList} {
set spawn_id [RouterLogin $ip $user $pass]
set timeout 30
foreach cmd $commandList {
send "$cmd\r"
expect {
"#" {
# you are good
puts "Command executed successfully"
}
eof {
# wait 5 minutes
after [expr {1000 * 60 * 5}]
if {[RouterPing $ip]} {
# ping was successful, relogin and resume
set spawn_id [RouterLogin $ip $user $pass]
} else {
# ping was not successful, abort execution
return false
}
}
timeout {
puts "INF: timeout"
return false
}
}
}
send "logout\r"
return true
}
set commandList [list command1 command2 command3]
RouterExec "42.0.1.11" "admin" "ram" $commandList