I have two questions:
In one of my tcl functions an array is declared and initialized as follows:
foreach port $StcPorts {
set dutport [lindex $DutPortsTmp $i]
set STCPort($dutport) [list 0 [lindex [lindex $cardlist $port] 0] [lindex [lindex $cardlist $port] 1]]
}
This is pretty confusing to me since I am new to tcl. Please help me to understand the above code on STCPort creation.
Question 2:
In the same file, STCPort is being used like this:
foreach dutport $DutPortsTmp {
set slot [lindex $STCPort($dutport) 1]
set port [lindex $STCPort($dutport) 2]
set hPort [stc::create port -under $hProject -location //$stcipaddress/$slot/$port -useDefaultHost False ]
lappend STCPort($dutport) $hPort
}
I have sourced this file and my requirement is to get the hPort value in another function residing in another file and work on it. Below is the function d_stc:
proc d_stc {args} {
global DutPorts STCPort
upvar 1 $STCPort _STCPort
#I am trying to get hPort by array index below:
foreach DutPort $DutPorts {
set card [lindex $_STCPort($DutPort) 1]
set port [lindex $_STCport($DutPort) 2]
set hport [lindex $_STCPort($DutPort) 3]
}
And, I am getting the following error:
can't read "STCPort": variable is array
while executing
"upvar 1 $STCPort _STCPort"
(procedure "d_stc" line 3)
I have used global to access the array in this function. But, where I am going wrong?
Thanks,
foreach port $StcPorts {
set dutport [lindex $DutPortsTmp $i]
set STCPort($dutport) [list 0 [lindex [lindex $cardlist $port] 0] [lindex [lindex $cardlist $port] 1]]
}
This snippet goes through the elements of the list StcPorts, which seem to be integers representing some kind of port numbers. For every port number, an array index (stored as dutport) is generated by taking the ith element from the list DutPortsTmp. The value of i is unexplained by this snippet. Also for everly port number, an array value in the array STCPort is created, with the index created above, and a value constructed like this:
Get the element in the list cardlist that corresponds to the integer index port (call that C)
Form a list from the three items 0, the first subitem in C, and the second subitem in C.
.
foreach dutport $DutPortsTmp {
set slot [lindex $STCPort($dutport) 1]
set port [lindex $STCPort($dutport) 2]
set hPort [stc::create port -under $hProject -location //$stcipaddress/$slot/$port -useDefaultHost False ]
lappend STCPort($dutport) $hPort
}
The list DutPortsTmp, which was used to create array indexes, reappears. We work with every item in the list, with the list item/array index again being named dutport. For every iteration, we look at the corresponding array item, setting slot to the second item of the array item's value (the first item in C, above) and port to the third item of the array item's value (the second item in C). The result of an invocation of stc::create port is assigned to the variable hPort, and the value of the array item is extended with a new list item, the value in hPort.
proc d_stc {args} {
global DutPorts STCPort
upvar 1 $STCPort _STCPort
#I am trying to get hPort by array index below:
foreach DutPort $DutPorts {
set card [lindex $_STCPort($DutPort) 1]
set port [lindex $_STCport($DutPort) 2]
set hport [lindex $_STCPort($DutPort) 3]
}
This should probably be:
proc d_stc {args} {
global DutPorts STCPort
foreach DutPort $DutPorts {
set hport [lindex $STCPort($DutPort) 3]
}
}
The corrected snippet again goes through the array indexes for STCPort, but this time DutPorts is used instead of DutPortsTmp to get the array indexes. The hport variable gets the value of the fourth item in each of the array items.
But: the value in hport is never used or returned anywhere, and the value is overwritten by a new value from each new item looked at.
upvar 1 $STCPort _STCPort
does not work since there is no string value in STCPort. If the procedure is called from the global level, this would work:
upvar 1 STCPort _STCPort
(See the difference? Not the value of STCPort, but the string "STCPort" itself.) You don't need this: the invocation of global DutPorts STCPort already makes the array STCPort visible inside the procedure.
Related
My goal is to find the next key in an array... below my data :
# Index increment may change, there is not necessarily continuity like this example.
# My $index can be 1,2,3,4,8,12,25,32...
# but the size of my array is about 100,000 elements.
for {set index 1} {$index < 100000} {incr index} {
set refdata($index,Pt,X) [expr {10 + $index}]
}
I need to know the next key to be able to build a geometric line... I did not find in the help a command that allows me to find the next key of my array so I created my own function below :
proc SearchNextKeyArrayElement {dataarray mykey} {
upvar $dataarray myarray
set mydata [lsort -dictionary [array names myarray]]
set index [lsearch $mydata $mykey]
if {$index > -1} {
return [lindex $mydata [expr {$index + 1}]]
}
return ""
}
foreach k [lsort -dictionary [array names refdata]] {
if {[string match "*,Pt,*" $k]} {
set nextkey [SearchNextKeyArrayElement refdata $k]
}
}
And it takes a long time...array nextelement command is maybe the solution...But I do not understand how to use it ?
Here's an example:
start a search with array startsearch
loop while array anymore is true
get the next key with array nextelement
tidy up with array donesearch
use try {} catch {} finally for safety
# array foreach
# to be subsumed in Tcl 8.7 by `array for`
# https://core.tcl.tk/tips/doc/trunk/tip/421.md
#
# example:
# array set A {foo bar baz qux}
# array foreach {key val} A {puts "name=$key, value=$val"}
#
# A note on performance: we're not saving any time with this approach.
# This is essentially `foreach name [array names ary] {...}
# We are saving memory: iterating over the names versus extracting
# them all at the beginning.
#
proc array_foreach {vars arrayName body} {
if {[llength $vars] != 2} {
error {array foreach: "vars" must be a 2 element list}
}
lassign $vars keyVar valueVar
# Using the complicated `upvar 1 $arrayName $arrayName` so that any
# error messages propagate up with the user's array name
upvar 1 $arrayName $arrayName \
$keyVar key \
$valueVar value
set sid [array startsearch $arrayName]
# If the array is modified while a search is ongoing, the searchID will
# be invalidated: wrap the commands that use $sid in a try block.
try {
while {[array anymore $arrayName $sid]} {
set key [array nextelement $arrayName $sid]
set value [set "${arrayName}($key)"]
uplevel 1 $body
}
} trap {TCL LOOKUP ARRAYSEARCH} {"" e} {
puts stderr [list $e]
dict set e -errorinfo "detected attempt to add/delete array keys while iterating"
return -options $e
} finally {
array donesearch $arrayName $sid
}
return
}
Generally speaking, Tcl arrays have no order at all; they can change their order on any modification to the array or any of its elements. The commands that iterate over the array (array for, array get, array names, and the iteration commands array startsearch/array nextelement/array anymore) only work with the current order. However, you can use array names to get the element names into a Tcl list (which is order preserving), sort those to get the order that you're going to iterate over, and then use foreach over that. As long as you're not adding or removing elements, it'll be fine. (Adding elements is sort-of OK too; you'll just not see them in your iteration.)
foreach key [lsort -dictionary [array names myarray]] {
ProcessElement $key $myarray($key)
}
By contrast, trying to just go from one element to the next will hurt a lot; that operation is not exposed.
Using the iteration commands is done like this:
set s [array startsearch myarray]
while {[array anymore myarray $s]} {
set key [array nextelement myarray $s]
ProcessElement $key $myarray($key)
}
Note that you don't get an option to sort the search. You won't see these used much in production code; doing array names or array get is usually better. And now (well, 8.7 is still in alpha) you've also got array for:
array for {key value} myarray {
ProcessElement $key $value
}
Efficient for large arrays, but still doesn't permit sorting; supporting direct sorting would require a different sort of storage engine on the back of the array.
This is why it's slow: You're sorting the array names once for the foreach command and then again for each element. Sort once and cache it, then you can iterate over it much more efficiently
set sorted_names [lsort -dictionary [array names refdata -glob {*,Pt,*}]]
set len [llength $sorted_names]
for {set i 0; set j 1} {$i < $len} {incr i; incr j} {
set this_name [lindex $sorted_names $i]
set next_name [lindex $sorted_names $j]
# ...
}
I want to save 7 variables incoming over a serial port. The transmission starts with an empty line, followed by 7 lines, each consisting of a single variable. No blanks but a carriage return at every line end. Each variable can also consists of blanks. This is carried out repeatedly.
If the empty line would cause a problem, it coud be omitted in my external device.
#!/ usr /bin/env wish
console show
set Term(Port) com5
set Term(Mode) "9600,n,8,1"
set result [list]
set data {}
proc receiver {chan} {
set data [gets $chan]
concat {*}[split $data \n]
set ::result [split $data "\n"]
#puts $data
#puts $::result
#foreach Element $::result {
#puts $Element}
#puts "Element 0 [lindex $::result 0]"
#puts "Element 1 [lindex $::result 1]"
return
}
set chan [open $Term(Port) r+]
fconfigure $chan -mode $Term(Mode) -translation binary -buffering none -blocking 0
fileevent $chan readable [list receiver $chan]
puts $data shows the following:
START
ChME3
562264
Lok3
Lok4
Lok6
All the 7 variables are visible but with empty lines inbetween. The empty line between "Lok4" and "Lok6" seems to be ok, since this is a variable consisting of blanks.
I tried to create a list with set ::result [split $data "\n"]. But that isn't working properly. With foreach Element $::result {puts $Element} the console shows the 7 variables:
START
ChME3
562264
Lok3
Lok4
.
Lok6
I have inserted the point between Lok4 and Lok6 manually here in the blockquote just for display purposes. In reality it's a variable consisting of only blanks.
Despite it looks like a list, if I try
puts "Element 0 [lindex $::result 0]"
puts "Element 1 [lindex $::result 1]"
it shows
Element 0 START
Element 1
Element 0 ChME3
Element 1
Element 0 562264
and so on.
Element 1 remains empty and Element 0 is consecutively assigned with each variable.
So it is clearly not a list. But I wonder, why foreach Element $::result {puts $Element}seems to work? What do I have to change to get a real list?
but I'm unable to retrieve it. Or do I have to create an own new list?
The result is retrieved using gets and turned into a list using split in this one step:
[split [gets $chan] {}]
To stash this list away, assign the list value to a variable that is scoped beyond the surrounding proc, e.g., a global or namespace variable:
set ::result [split [gets $chan] {}]
In context:
proc receiver {chan} {
set data [gets $chan]
set ::result [concat {*}[split $data \n]]
# set ::result [split [gets $chan] {}]
# puts $::result; # debug print-out
return
}
GUI integration
I have already created such a GUI where I want to put these variables
into labels
Connect your label widget to the global variable ::result, so the label becomes updated upon changes to the variable in proc receiver.
label .l -textvar ::result
I tried create a procedure that gets a list from the user and prints the min and max values of the list.
I think that the problem is passing the list as an argument to the procedure.
Here is my code:
proc minmaxlist {mylist} {
lsort -integer $mylist
puts "my list is: $mylist\n"
#puts "the length is $argc\n"
set min [lindex $mylist 0]
set max [lindex $mylist [llength[mylist] -1]]
puts "max is $max"
puts "min is $min"
}
set mylist [list $argv]
minmaxlist $mylist
I found that if my list is {5 7 0} my list[0] gets the value of 5 7 0 instead of 5.
Thanks!
The problem is not passing the list to the procedure, but the [list $argv]. The argv variable already contains a list. By wrapping it in another list command, you end up with a list with only one element (which itself is another list). That single element will then end up to be both the minimum and maximum value. So, just pass $argv to the proc, or set mylist to $argv, instead of [list $argv].
Then in your proc, you sort the list and discard the result. You will want to store the result in a variable. You can reuse mylist for that: set mylist [lsort -integer $mylist].
You may also have noticed that your statement to get the max value doesn't work. You probably meant to do set max [lindex $mylist [expr {[llength $mylist] - 1}]]. The last element can more easily be obtained via set max [lindex $mylist end]
Im searching all over the internet , i guess im searching not the right keywords
i tried most of them :)
i want to create in tcl/bash a proc with hyphen flags to get arguments with flags from the user
ex.
proc_name -color red -somethingselse black
It's very easy to do, actually. This code allows abbreviated option names, flag options (-quxwoo in the example) and the ability to stop reading options either with a -- token or with a non-option argument appearing. In the example, unknown option names raise errors. After passing the option-parsing loop, args contains the remaining command-line arguments (not including the -- token if it was used).
proc foo args {
array set options {-bargle {} -bazout vampires -quxwoo 0}
while {[llength $args]} {
switch -glob -- [lindex $args 0] {
-bar* {set args [lassign $args - options(-bargle)]}
-baz* {set args [lassign $args - options(-bazout)]}
-qux* {set options(-quxwoo) 1 ; set args [lrange $args 1 end]}
-- {set args [lrange $args 1 end] ; break}
-* {error "unknown option [lindex $args 0]"}
default break
}
}
puts "options: [array get options]"
puts "other args: $args"
}
foo -barg 94 -quxwoo -- abc def
# => options: -quxwoo 1 -bazout vampires -bargle 94
# => other args: abc def
This is how it works. First set default values for the options:
array set options {-bargle {} -bazout vampires -quxwoo 0}
Then enter a loop that processes the arguments, if there are any (left).
while {[llength $args]} {
During each iteration, look at the first element in the argument list:
switch -glob -- [lindex $args 0] {
String-match ("glob") matching is used to make it possible to have abbreviated option names.
If a value option is found, use lassign to copy the value to the corresponding member of the options array and to remove the first two elements in the argument list.
-bar* {set args [lassign $args - options(-bargle)]}
If a flag option is found, set the corresponding member of the options array to 1 and remove the first element in the argument list.
-qux* {set options(-quxwoo) 1 ; set args [lrange $args 1 end]}
If the special -- token is found, remove it from the argument list and exit the option-processing loop.
-- {set args [lrange $args 1 end] ; break}
If an option name is found that hasn't already been dealt with, raise an error.
-* {error "unknown option [lindex $args 0]"}
If the first argument doesn't match any of the above, we seem to have run out of option arguments: just exit the loop.
default break
Documentation: array, break, error, lassign, lindex, llength, proc, puts, set, switch, while
With array set, we can assign the parameters and their values into an array.
proc getInfo {args} {
# Assigning key-value pair into array
# If odd number of arguments passed, then it should throw error
if {[catch {array set aInfo $args} msg]} {
return $msg
}
parray aInfo; # Just printing for your info
}
puts [getInfo -name Dinesh -age 25 -id 974155]
will produce the following output
aInfo(-age) = 25
aInfo(-id) = 974155
aInfo(-name) = Dinesh
The usual way to handle this in Tcl is by slurping the values into an array or dictionary and then picking them out of that. It doesn't offer the greatest amount of error checking, but it's so easy to get working.
proc myExample args {
# Set the defaults
array set options {-foo 0 -bar "xyz"}
# Read in the arguments
array set options $args
# Use them
puts "the foo option is $options(-foo) and the bar option is $options(-bar)"
}
myExample -bar abc -foo [expr {1+2+3}]
# the foo option is 6 and the bar option is abc
Doing error checking takes more effort. Here's a simple version
proc myExample args {
array set options {-foo 0 -bar "xyz"}
if {[llength $args] & 1} {
return -code error "must have even number of arguments in opt/val pairs"
}
foreach {opt val} $args {
if {![info exist options($opt)]} {
return -code error "unknown option \"$opt\""
}
set options($opt) $val
}
# As before...
puts "the foo option is $options(-foo) and the bar option is $options(-bar)"
}
myExample -bar abc -foo [expr {1+2+3}]
# the foo option is 6 and the bar option is abc
# And here are the errors it spits out...
myExample -spregr sgkjfd
# unknown option "-spregr"
myExample -foo
# must have even number of arguments in opt/val pairs
#flag defaults
set level 1
set inst ""
# Parse Flags
while {[llength $args]} {
set flag [lindex $args 0]
#puts "flag: ($flag)"
switch -glob $flag {
-level {
set level [lindex $args 1]
set args [lrange $args 2 end]
puts "level:($level) args($args)"
} -inst {
set autoname 0
set inst [lindex $args 1]
set args [lrange $args 2 end]
puts "inst:($inst) args($args)"
} -h* {
#help
puts "USAGE:"
exit 1
} -* {
# unknown option
error "unknown option [lindex $args 0]"
} default break
}
}
# remaining arguments
set filename "$args"
puts "filename: $args"
I'm producing a TCL procedure that will return a list of arrays of devices under a switch. The definition is an XML file that is read. The resulting lists of XML entries are parsed using a recursive procedure and the device attributes are placed in an array.
Each array is then placed in a list and reflected back to the caller. My problem is that when I print out the list of devices, the last device added to the list is printed out each time. The contents of the list is all duplicates.
Note: I'm using the excellent proc, 'xml2list' that was found here. I'm sorry, I forgot who submitted this.
The following code illustrates the problem:
source C:/src/tcl/xml2list.tcl
# Read and parse XML file
set fh [open C:/data/tcl/testfile.xml r]
set myxml [read $fh]
set mylist [xml2list $myxml]
array set mydevice {}
proc devicesByName { name thelist list_to_fill} {
global mydevice
global set found_sw 0
upvar $list_to_fill device_arr
foreach switch [lindex $thelist 2] {
set atts [lindex $switch 1]
if { [lindex $switch 0] == "Switch" } {
if { $name == [lindex $atts 3] } {
set found_sw 1
puts "==== Found Switch: $name ===="
} else {
set found_sw 0
}
} elseif { $found_sw == 1 && [string length [lindex $atts 3]] > 0 } {
set mydevice(hdr) [lindex $switch 0]
set mydevice(port) [lindex $atts 1]
set mydevice(name) [lindex $atts 3]
set mydevice(type) [lindex $atts 5]
puts "Device Found: $mydevice(name)"
set text [lindex $switch 2]
set mydevice(ip) [lindex [lindex $text 0] 1]
lappend device_arr mydevice
}
devicesByName $name $switch device_arr
}
}
#--- Call proc here
# set a local array var and send to the proc
set device_arr {}
devicesByName "Switch1" $mylist device_arr
# read out the contents of the list of arrays
for {set i 0} {$i<[llength $device_arr]} {incr i} {
upvar #0 [lindex $device_arr $i] temp
if {[array exists temp]} {
puts "\[$i\] Device: $temp(name)-$temp(ip)"
}
}
The XML file is here:
<Topology>
<Switch ports="48" name="Switch1" ip="10.1.1.3">
<Device port="1" name="RHEL53-Complete1" type="host">10.1.1.10</Device>
<Device port="2" name="Windows-Complete1" type="host">10.1.2.11</Device>
<Device port="3" name="Solaris-Complete1" type="host">10.1.2.12</Device>
</Switch>
<Switch ports="36" name="Switch2" ip="10.1.1.4">
<Device port="1" name="Windows-Complete2" type="host">10.1.3.10</Device>
</Switch>
<Router ports="24" name="Router1" ip="10.1.1.2">
<Device port="1" name="Switch1" type="switch">10.1.1.3</Device>
<Device port="2" name="Switch2" type="switch">10.1.1.4</Device>
</Router>
</Topology>
If my code blocks look bad, please excuse that. I followed the directions as I read them, but it didn't look correct. I could not fix it, so just posted anyway.
Thanks in advance...
Arrays in tcl are not values. Therefore they don't behave like regular variables. They are in fact something special like filehandles or sockets.
You cannot assign an array to a list like that. Doing:
lappend device_arr mydevice
simply appends the string "mydevice" to the list device_arr. That string happens to be the name of a global variable so that string may be used later to access that global variable.
To build up a key-value data structure what you want is a dict. You can think of a dict as a special list that has even numbers of elements in the format: {key value key value}. In fact, this data structure works even on very old versions of tcl before the introduction of dicts because the foreach loop in tcl can be used to process key-value pairs.
So what you want is to create a new $mydevice dict each loop and use [dict set] to assign the values.
Alternatively you can keep most of your code and change your lappend to:
lappend device_arr [array get mydevice]
This works because [array get] returns a key-value list which can be treated as a dict. You can later access the data using the dict command.
Array variables can't be used as values. To put the contents of one into a list element, send it to a proc, write it to a file etc, convert it to list form (key, value, key, value...) with array get.
lappend device_arr [array get mydevice]
To use it later, write the list back to an array with array set.
foreach device_l $device_arr {
#array unset device
array set device $device_l
puts "$device(name)-$device(ip)"
}
Note that array set doesn't erase the old keys in the destination array, so if you use it in a loop and the key names aren't always the same, you need to clear the array every iteration.
You can store this information in two ways using arrays . First is as a multi-dimensional array, in this case a three dimensional array and the second is a one dimensional array storing a list that can be converted easily to an array later for accessing data at a later time.
For the 3d array the key would be Switch Name,device_port,dataname you would change your erroneous temporary myDevice and lappend code to
# attr is a list of { attributename1 value1 ... attributenameN valueN}
array set temp $attr
set port $temp(port)
set text [lindex $switch 2]
set ip [lindex [lindex $text 0] 1]
# name already set to "Switch1" etc
foreach f [array names temp ] {
set device_arr($name,$port,$f) $temp($f)
}
set device_arr($name,$port,ip) $ip
array unset temp
this code results in the following ( when parray device_arr
parray device_arr
device_arr(Switch1,1,name) "Switch1"
device_arr(Switch1,1,port) 1
device_arr(Switch1,1,type) "RedHat .."
device_arr(Switch1,1,ip) 10..
device_arr(Switch1,2,name) "Switch1"
device_arr(Switch1,2,port) 1
device_arr(Switch1,2,type) "RedHat .."
device_arr(Switch1,2,ip) 10..
...
device_arr(Switch2,1,name) "Switch1"
device_arr(Switch2,1,port) 1
device_arr(Switch2,1,type) "Windows Complete"
device_arr(Switch2,1,ip) 10..
....
to find ip of Switch1 port2 you would:
puts "the ip of Switch1 port 2 is $device_arr(Switch1,2,ip)"
Note lots of data duplication but you can access all data directly without having to go to an intermediate step to get to the data as in the next scheme
# attr is a list of { attributename1 value1 ... attributenameN valueN}
set data $attr
array set temp $attr
set text [lindex $switch 2]
set ip [lindex [lindex $text 0] 1]
lappend data ip $ip
set key "$name,$temp(port)"
# name already set to "Switch1" etc
set device_arr($name,$port) $data
array unset temp
doing a parray device_arr gives:
device_arr(Switch1,1) { port "1" name "RHEL53-Complete1" type "host" ip 10.1.1.10 }
device_arr(Switch1,2) { port "2" name "Windows-Complete1" type "host" ip 10.1.2.11}
....
to find the ip of swtich1 port 2 you would
array set temp $device_array(Switch1,2)
puts "ip of device 2 is $temp(ip)"