I have a loop that looks something like:
foreach x {a b} {
set type_$x [some_function_here]
set N_$x [function type_$x]
}
The problem is that I want to dereference type_$a in the second line and use its value as the function argument.
However doing:
set N_$x [function $type_$x]
doesn't work and neither did any of the other combinations I tried with the subst command did either.
How do I solve this?
Three possibilities, in order of how much I think you should prefer them.
Arrays
By far the easiest technique is to use an array instead:
foreach x {a b} {
set type($x) [some_function_here]
set N($x) [function $type($x)]
}
This does change how the rest of your program sees things, so it's not a zero-impact technique, but it's very easy; I think this is the most recommended way of doing this.
Local Aliases
Alternatively, use upvar 0 to make a local alias to the variable-named variable:
foreach x {a b} {
upvar 0 type_$x typex N_$x Nx
set typex [some_function_here]
set Nx [function $typex]
}
Internally, the names just resolve to the same storage cell, so this is an efficient technique (though upvar 0 is quite tricky!)
Single-Argument set
Finally, you can read from an arbitrarily-named variable using the set command with one argument; the $ syntax is arguably just syntactic sugar for that.
foreach x {a b} {
set type_$x [some_function_here]
set N_$x [function [set type_$x]]
}
It's usually something of a code-smell if you're doing that a lot, and it usually indicates that you should be using an array.
set N_$x [function [set type_$x]]
The $var notation is basically shorthand for the command [set var], and sometimes you need to use the command rather than the shorthand.
There is no way to tell the command evaluation "when I say '$type_$x' I want you to hold up evaluating the first $ until the whole variable name has been put together". That is, unless you write it as [set type_$x], in which case the name type_a or type_b is constructed first, and then passed to set.
Documentation: set, evaluation syntax
Some others ways to do a deference of a variable with dynamic name.
% set x a
a
% set type_$a stackoverflow
stackoverflow
% expr \$type_$a
stackoverflow
% subst $[subst type_$a]
This is bit complex, comparable to other methods though.
Related
I'm learning about Tcl just now. I've seen just a bit of it, I see for instance to create a variable (and initialize it) you can do
set varname value
I am familiarizing with the fact that basically everything is a string, such as "value" above, but "varname" gets kind of a special treatment I guess because of the "set" built-in function, so varname is not interpreted as a string but rather as a name.
I can later on access the value with $varname, and this is fine to me, it is used to specify varname is not to be considered as a string.
I'm now reading about lists and a couple commands make me a bit confused
set colors {"aqua" "maroon" "cyan"}
puts "list length is [llength $colors]"
lappend colors "purple"
So clearly "lappend" is another one of such functions like set that can interpret the first argument as a name and not a string, but then why didn't they make it llength the same (no need for $)?
I'm thinking that it's just a convention that, in general, when you "read" a variable you need the $ while you don't for "writing".
A different look at the question: what Tcl commands are appropriate for list literals?
It's valid to count the elements of a list literal:
llength {my dog has fleas}
But it doesn't make sense to append a new element to a literal
lappend {my dog has fleas} and ticks
(That is actually valid Tcl, but it sets the odd variable ${my dog has fleas})
this is more sensible:
set mydog {my dog has fleas}
lappend mydog and ticks
Names are strings. Or rather a string is a name because it is used as a name. And $ in Tcl means “read this variable right now”, unlike in some other languages where it really means “here is a variable name”.
The $blah syntax for reading from a variable is convenient syntax that approximately stands in for doing [set blah] (with just one argument). For simple names, they become the same bytecode, but the $… form doesn't handle all the weird edge cases (usually with generated names) that the other one does. If a command (such as set, lappend, unset or incr) takes a variable name, it's because it is going to write to that variable and it will typically be documented to take a varName (variable name, of course) or something like that. Things that just read the value (e.g., llength or lindex) will take the value directly and not the name of a variable, and it is up to the caller to provide the value using whatever they want, perhaps $blah or [call something].
In particular, if you have:
proc ListRangeBy {from to {by 1}} {
set result {}
for {set x $from} {$x <= $to} {incr x $by} {
lappend result $x
}
return $result
}
then you can do:
llength [ListRangeBy 3 77 8]
and
set listVar [ListRangeBy 3 77 8]
llength $listVar
and get exactly the same value out of the llength. The llength doesn't need to know anything special about what is going on.
In my scripts, when using a variable, I generally empty the contents of a variable to ensure that the list appends are clean. Something like the following
set var1 [list]
foreach var2 {a b c} {
lappend var1 $var2
}
But it seems like unsetting the variable first would accomplish the same thing. Something like this
unset -nocomplain var1
foreach var2 {a b c} {
lappend var1 $var2
}
Is there any advantage for using one vs the other?
It doesn't make any difference in this case. If I was to write such a loop in my own code, I would be more likely to use set var {} since that is the empty list literal (as well as being the empty string, the empty dictionary, the empty script, etc.) but there isn't any execution time difference to speak of. It just reflects how I think about scripts.
Of course, if you are doing something where it does matter, use the right one for that case.
As Donal wrote, set var {}. Internally, the same value will be assigned regardless of whether you assign {}, [list] etc. Yes, it will shimmer, and no, it won't be a problem.
Regarding set vs unset: while you can use them as you see fit, they mostly serve different patterns. In the assign-empty-value pattern, you want a variable ready for writing or reading, with a predefined, empty value. In the remove-from-scope pattern you want the name to be unused (it won't be unusable: you can still assign to / create it). Unless you're after something like the second pattern, you probably won't have much serious use for unset.
I have searched for lappend $var1 $var2, but don't find any exact answer, how it will be executed.
% set a {a b c}
a b c
% set b {d e}
d e
% puts [lappend $c $b]
can't read "c": no such variable >>> here it throws error like variable not exist
% puts [lappend $a $b]
{d e} >>> here it doesn't throw any error, can someone explain it and how to print the value of $a, if $a is a new variable
% puts $$A
can't read "A": no such variable
% puts $$a
$a b c
% puts ${$a}
can't read "$a": no such variable
Tcl's got a two level syntax that it applies rigorously to everything. The first level is the Tcl generic syntax, which takes:
lappend $var1 $var2
and parses it out to three words: lappend, a word obtained by reading the variable var1, and a word obtained by reading the variable var2.
Then Tcl dispatches to the command named by the first word (lappend, a Tcl built-in) which applies command syntax handling. In the case of lappend, it's pretty simple: the first argument names a variable and the second and subsequent arguments are words to append to the list in the named variable.
In your case, the first argument that names a variable is obtained by reading another variable (var1) and the value to append to the list is coming from a variable (var2); a name like a b c d e is a legal variable name in Tcl, but it's really awkward to use. And the chance is very high that you don't want to write that: putting variable names in a variable is usually an indicator of confusing code. You can do it, but you hardly ever want to do it (except when you're using the variable name with upvar). You probably really meant to write:
lappend var1 $var2
Tcl is very exact about the distinction between variable names and variable contents. The $ is not decorative! It's there to say “read this variable, right now”, and $var1 is virtually equivalent to [set var1] in semantic terms. (The $ shorthand was later, a Tcl 2.0 feature from way back in the day!)
Tcl also doesn't allow double-dereferencing with $$vrbl. In the rare cases you need it, you do [set $vrbl]. And if you do that, you probably should immediately see if you can use an array instead as that's typically a better choice…
lappend's first parameter is a variable name, not a value. Therefore, in general, it should be:
lappend var1 $var2
where both var1 and var2 are list variables. See the Tcl lappend man page for more details.
x is a list of device names (device-1, device-2, device-3)
There is a variable created for each device1 by concatenating the string port so you end up with $device-1port.
looping over x creates
[expr $${x}port-2000 ] #x is device-1 so it is trying $device-1port-2000 which throws error.
I would like to get the numeric value of $device-1port into a variable without a dash.
set xvar $${x}port
[expr $xvar-2000 ]
or can i wrap the $${x}port in something within the expr statement.
To read a variable with interpolations in its name, use single-argument set:
set withoutadash [set device-${x}port]
Generally, it's better to use arrays for this kind of thing.
One of the nicest ways to work with such complex variables is to use the upvar command to make a local “nice” alias to the variable. In particular, upvar 0 makes a local alias to a local variable; slightly tricky, but a known technique.
upvar 0 ${x}port current_port
Now, we have any read, write or unset of current_port is the same as a read/write/unset of the port with the awkward name, and you can write your code simply:
puts [expr { $current_port - 2000 }]
set current_port 12345
# etc.
The alias will be forgotten at the end of the current procedure.
Of course, you probably ought to consider using arrays instead. They're just simpler and you don't need to work hard with computed variable names:
set x 1
set device($x,port) 12345
puts [expr {$device($x,port) - 2000}]
here is an example of what I'm trying to do.
set t SNS
set ${t}_top [commands that return value]
Want to get the info stored at ${t}_top
puts “${t}_top”
SNS_top (really want the data stored there?)
Thought it was : ${{$t}_top} , maybe that was perl but {} inside the {} do not work.
One of the really interesting things about Tcl is that you can create variable names dynamically, as you are doing in the question you posted. However, this makes it tricky to write and makes your code harder than necessary to understand.
Instead of trying to figure out how to do the equivalent of ${{$t}_top}, it's arguably better to avoid the problem altogether. You can do that by using an associative array.
For example, instead of this:
set t SNS
set ${t}_top [commands that return value]
...
puts [set ${t}_top]
Do this:
set t SNS
set top($t) [commands that return value]
...
puts $top($t)
Most people agree that the latter example is much more readable.
try
puts [set ${t}_top]
Each line of code in Tcl is run through the substitution phase (in which variables, commands, etc are substituted) only once... generally. As such, something like
set var1 1
set var2 var1
set var3 $$var2
won't wind up with var3 equaling 1, since the substitutor will replace "$$var2" with "the value of the variable named '$var2' (literally)" and stop.
What you need it to either go about things another way or to force another round of substitution. The other way is generally to avoid needing a second round of substitution (as shown by Jackson):
set var3 [set $var2]
Here, the $var2 is replaced, during substitution, by "var1"... then [set var1] returns 1... then var3 gets set to the value of "1"... and you're good.
The syntax
puts [expr $${t}_top]
works as well, and avoids using the 'set' operation so a syntax error shouldn't overwrite your data.