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}]
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.
I have set 3 global variables as below in a file FILE1:
set VAR1 2
set VAR2 3
set VAR3 4
Now I want to use these 3 variables in another file FILE2 in iterative way:
Means, something like this:
for {set a 1} {$a < 4} {incr a} {
$::VAR$a
}
where VAR$a - should be incremented each time to VAR1,VAR2,VAR3 etc...
But if I try like this using the global variable I get error in tcl
Any better solution for this?
Either make your meaning clearer to the interpreter
set ::VAR$a
(you are aware that this is just getting the variable's value without doing anything with the value, that is, a pointless operation, right?)
Or use an array, which is basically a two-part variable name:
set ::VAR($a)
in which case you need to initialize as an array:
set VAR(1) 2
etc, or
array set VAR {1 2 2 3 3 4}
The reason why $::VAR$a doesn't always work is AFAICT that the variable substitution becomes ambiguous. Given these definitions:
set foobar 1
set a foo
set b bar
what should $a$b substitute into? To avoid ambiguity, the substitution rules are kept simple: the first substitution stops before the second dollar sign, and the whole expression evaluates to the string foobar. How about $$a$b to substitute the value of foobar, then? No, a dollar-sign followed directly by a character that can't be a part of a variable name means that the first dollar sign becomes just a dollar sign: you get $foobar. The best way to handle this is to reduce the levels of substitution using the set command to get a value: set $a$b. Bottom line: variable substitution using $ does not always work well, but the set always does the job.
Documentation:
set,
Summary of Tcl language syntax
I can't understand how assignments and use of variables work in Tcl.
Namely:
If I do something like
set a 5
set b 10
and I do
set c [$a + $b]
Following what internet says:
You obtain the results of a command by placing the command in square
brackets ([]). This is the functional equivalent of the back single
quote (`) in sh programming, or using the return value of a function
in C.
So my statement should set c to 15, right?
If yes, what's the difference with
set c [expr $a + $b]
?
If no, what does that statement do?
Tcl's a really strict language at its core; it always follows the rules. For your case, we can therefore analyse it like this:
set c [$a + $b]
That's three words, set (i.e., the standard “write to a variable” command), c, and what we get from evaluating the contents of the brackets in [$a + $b]. That in turn is a script formed by a single command invocation with another three words, the contents of the a variable (5), +, and the contents of the b variable (10). That the values look like numbers is irrelevant: the rules are the same in all cases.
Since you probably haven't got a command called 5, that will give you an error. On the other hand, if you did this beforehand:
proc 5 {x y} {
return "flarblegarble fleek"
}
then your script would “work”, writing some (clearly defined) utter nonsense words into the c variable. If you want to evaluate a somewhat mathematical expression, you use the expr command; that's it's one job in life, to concatenate all its arguments (with a space between them) and evaluate the result as an expression using the documented little expression language that it understands.
You virtually always want to put braces around the expression, FWIW.
There are other ways to make what you wrote do what you expect, but don't do them. They're slow. OTOH, if you are willing to put the + first, you can make stuff go fast with minimum interference:
# Get extra commands available for Lisp-like math...
namespace path ::tcl::mathop
set c [+ $a $b]
If you're not a fan of Lisp-style prefix math, use expr. It's what most Tcl programmers do, after all.
set c [$a + $b]
Running the above command, you will get invalid command name "5" error message.
For mathematical operations, we should rely on expr only as Tcl treats everything as string.
set c [expr $a + $b]
In this case, the value of a and b is passed and addition is performed.
Here, it is always safe and recommended to brace the expressions as,
set c [expr {$a+$b}]
To avoid any possible surprises in the evaluation.
Update 1 :
In Tcl, everything is based on commands. It can a user-defined proc or existing built-in commands such as lindex. Using a bare-word of string will trigger a command call. Similarly, usage of [ and ] will also trigger the same.
In your case, $a replaced with the value of the variable a and since they are enclosed within square brackets, it triggers command call and since there is no command with the name 5, you are getting the error.
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.
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.