Reputation: 31
I have the following code :
set arr1(a1) t1
set arr2(a2) t2
set l1 {}
lappend l1 arr1
lappend l1 arr2
set arr3(a3) $l1
foreach names [array names arr3] {
set value $arr3($names)
puts "names = $names, value = $value"
foreach ar $value {
if {[array exists $ar]} {
puts "$ar is an array"
foreach {key val} [array get $ar] {
set d1 $ar($key)
puts "ar key = $key value = $val "
}
}
}
}
but when I run the tcl script it fails for the line "set d1 $ar($key)" . The error msg is 'can't read "ar(a1)": variable isn't array' . Can you please suggest what is causing the error and how do I resolve the same.
Upvotes: 3
Views: 6535
Reputation: 137557
When you use the syntax $ar($key)
, you are looking up the key $key
in the array ar
and returning its value. This is how Tcl is defined to work, it's in the basic language syntax. However, you're using the ar
variable to hold a scalar value, not an array (the two are completely separate; arrays aren't values, though lists and dictionaries are). That's why you're getting the error message.
To read from an array that is named in a variable, you either need to use a longer piece of syntax so that you substitute the variable name and then read from that variable (Tcl doesn't do this for you by default, since it's quite dangerous if you're not prepared for it) or you need to make an alias to the named array variable.
set
set d1 [set ${ar}($key)]
This works because $…
is really (under the hood) an alias for set
with a single argument. (Well, except that it doesn't actually call the command; they both call the same C API.) We use the ${...}
form to limit what the initial $
uses as its variable name. Be aware that if you put an array element name in ar
, you'll get odd results from this.
upvar 0 $ar theAlias
set d1 $theAlias($key)
The upvar
command links variables together, and in particular when used with 0
as its first argument, it aliases variables in the current scope. By establishing theAlias
as a fixed alias to the actual array (the one named by $ar
), we can then access it just like a normal array. You could also alias directly to an element:
upvar 0 ${ar}($key) theAlias
set d1 $theAlias
Note the same syntax as used with the set
solution above; we want the name of the element, not to read it. (Warning: do not alias to elements of the global env
array; the code that couples to the system environment variables does not work in a friendly way with aliases.)
The main issue with using upvar
is that you can't turn theAlias
back into a non-aliased variable (though you can retarget the alias by calling upvar
again) other than by throwing away the current stack frame (trivial for a procedure body, not too hard for a namespace via namespace delete
, but problematic with the global namespace as deleting that terminates the whole Tcl interpreter).
Upvotes: 4