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]
Related
Suppose I have a list of variable length. I will just use length of 3 as an example
set inst_list [list a b c]
Now also suppose I have a variable:
set add_string "1"
I want to be able to add the $add_string variable to the last element in the list. Note that the list has a variable length and is not always 3.
The output I would want in the above example is:
a b c1
I know that if this were a fixed size list I could do something like
concat [lindex $inst_list 2]$add_string
but this would only give me "c1" not the full list with "c1" at the end. Also this does not account for variable list size of $inst_list.
lset and string cat are appropriate here:
lset inst_list end [string cat [lindex $inst_list end] "1"]
string cat appears in Tcl v8.6
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 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.
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)"
I want to create a list and each element of it is an array, similarly to an array of structs in C language.
Can it be done in TCL and how if it can? thanks very much!
I did some try but it failed...
tcl>set si(eid) -1
tcl>set si(core) 0
tcl>set si(time) 0
tcl>lappend si_list "$si"
Error: can't read "si": variable is array
You can't create a list of arrays, but you can create a list of dicts which is functionally the same thing (a mapping from keys to values):
set mylist [list [dict create a 1 b 2] [dict create a 4 b 5]]
puts [dict get [lindex $mylist 1] a]
To do it as arrays you need to use [array get] and [array set] to change the array into a string:
set si(eid) -1
set si(core) 0
set si(time) 0
lappend si_list [array get si]
And to get it back out
array set newsi [lindex $si_list]
puts $newsi(eid)
dicts let you work on the {name value} lists directly.
One way to do this on versions of Tcl that don't include dict is to use upvar.
To do this, add the names of the array variables to your list:
set si(eid) -1
set si(core) 0
set si(time) 0
lappend si_list "si"
Then to get your array back, do this:
upvar #0 [lindex $si_list 0] newsi
puts $newsi(eid)
You could also use the ::struct::record package from tcllib for something like that.