I need to assign my final variable with the following string UDP6:[2001:172:16:21::36]
set ipAddr1 "UDP6,2001:172:16:21::36"
set ipAddrArr [split $ipAddr1 ","]
set ipAddrArr11 [lindex $ipAddrArr 0]
set ipAddrArr12 [lindex $ipAddrArr 1]
set tmp ":\["
set ipAddr1Part [join "$ipAddrArr11 $ipAddrArr12" $tmp]
set tmp1 "]"
set ipAddrFinal [join "$ipAddr1Part$tmp1"]
When I run the tcl script, it gives invalid command name as 2001:172:16:21::36.
I have printed ipAddrFinal value , it gives the expected one UDP6:[2001:172:16:21::36]
pls help me out? what am I missing
The script as you have written it works fine; it assigns the string UDP6:[2001:172:16:21::36] to the variable ipAddrFinal. However, since it contains characters that are Tcl metacharacters in some contexts, I suspect that you are then using the string in an unsafe way, most likely with eval or possibly with subst or uplevel. If you look at the stack trace of the error (in the errorInfo global variable by default) you should be told pretty exactly where the offending code is; it might give a few places you need to look, but it usually isn't too hard to hunt down where the problem originates from.
If your problem comes from uplevel, you are probably going to need to use list to construct a command; 99.99% of all problems with uplevel are handled that way. If your problems come from eval, the chance's good that you need to switch to using expansion syntax. If your problems come from subst or are otherwise still deeply confusing, check back with us (with your stack trace if you are not sure where the problem is coming from).
Example of a fix for eval:
Change:
set action "puts \"IP\\ address\\ is\\ $ipAddrFinal\""
eval $action
to:
set action [list puts "IP address is $ipAddrFinal"]
{*}$action
NB: The error from doing the eval is a reasonable example too:
invalid command name "2001:172:16:21::36"
while executing
"2001:172:16:21::36"
("eval" body line 1)
invoked from within
"eval $action"
Note that it says that it's in an eval, and that points squarely to unsafe script construction. The list command does safe script construction as one of its bonus superpowers.
Related
I am trying to run a tcl script through .bat file. I want to read some cmd arguments in the tcl script. Below is my code:
Command to run:
D:\Cadence\Sigrity2021.1\tools\bin\PowerSI.exe -tcl abcd.tcl %new_var%.spd %new_file_name%
Below is how I am trying to read the variable in the tcl file:
sigrity::open document [lindex $argv 0] {!}
It open up the Cadence Sigrity, but I see the below error:
How do I read cmd argument in tcl?
If you have no other way to do it that you can find (and it sounds like that might be the case) then you can fake it by writing a helper file with content like this, filling in the real arguments in the appropriate places:
# Name of script to call
set ::argv0 "abcd.tcl"
# Arguments to pass
set ::argv {}
lappend ::argv "%new_var%.spd"
lappend ::argv "%new_file_name%"
# Number of arguments (rarely used)
set ::argc [llength $::argv]
# Do the call
source $::argv0
Then you can pass that file to PowerSI and it will set things up and chain to the real file. It's messy, but practical.
If you're writing this from Tcl, use the list command to do the quoting of the strings (instead of putting them in double quotes) as it will do exactly the right thing for you. If you're writing the file from another language, you'll want to make sure you put backslashes in before \, ", $ and [ characters. The fiddlyness of doing that depends on your language.
I'm trying to create some read-only variables to use with code evaluated in a safe interp. Using trace, I can generate an error on attempts to set them, but not when using unset:
% set foo bar
bar
% trace add variable foo {unset write} {apply {{var _ op} { error "$var $op trace triggered" }}}
% set foo bar
can't set "foo": foo write trace triggered
% unset foo
%
Indeed, I eventually noticed the documentation even says in passing:
Any errors in unset traces are ignored.
Playing around with different return codes, including custom numbers, they all seem to be ignored. It doesn't trigger an interp bgerror handler either. Is there any other way to raise an error for an attempt to unset a particular variable?
There really isn't. The key problem is that there are times when Tcl is going to unset a variable when that variable really is going to be deleted because its containing structure (a namespace, stack frame or object, and ultimately an interpreter) is also being deleted. The variable is doomed at that point and user code cannot prevent it (except by the horrible approach of never returning from the trace, of course, which infinitely postpones the death and puts everything in a weird state; don't do that). There's simply nowhere to resurrect the variable to. Command deletion traces have the same issue; they too can be firing because their storage is vanishing. (TclOO destructors are a bit more protected against this; they try to not lose errors — there's even pitching them into interp bgerror as a last resort — but still can in some edge cases.)
What's more, there's currently nothing in the API to allow an error message to bubble out of the process of deleting a namespace or call frame. I think that would be fixable (it would require changing some public APIs) but for good reasons I think the deletion would still have to happen, especially for stack frames. Additionally, I'm not sure what should happen when you delete a namespace containing two unset-traced variables whose traces both report errors. What should the error be? I really don't know. (I know that the end result has to be that the namespace is still gone, FWIW, but the details matter and I have no idea what they should be.)
I'm trying to create some read-only variables to use with code evaluated
Schelte and Donal have already offered timely and in-depth feedback. So what comes is meant as a humble addition. Now that one knows that there variables traces are executed after the fact, the below is how I use to mimick read-only (or rather keep-re_setting-to-a-one-time-value) variables using traces (note: as Donal explains, this does not extend to proc-local variables).
The below implementation allows for the following:
namespace eval ::ns2 {}
namespace eval ::ns1 {
readOnly foo 1
readOnly ::ns2::bar 2
readOnly ::faz 3
}
Inspired by variable, but only for one variable-value pair.
proc ::readOnly {var val} {
uplevel [list variable $var $val]
if {![string match "::*" $var]} {
set var [uplevel [list namespace which -variable $var]]
}
# only proceed iff namespace is not under deletion!
if {[namespace exists [namespace qualifiers $var]]} {
set readOnlyHandler {{var val _ _ op} {
if {[namespace exists [namespace qualifiers $var]]} {
if {$op eq "unset"} {
::readOnly $var $val
} else {
set $var $val
}
# optional: use stderr as err-signalling channel?
puts stderr [list $var is read-only]
}
}}
set handlerScript [list apply $readOnlyHandler $var $val]
set traces [trace info variable $var]
set varTrace [list {write unset} $handlerScript]
if {![llength $traces] || $varTrace ni $traces} {
trace add variable $var {*}$varTrace
}
}
}
Some notes:
This is meant to work only for global or otherwise namespaced variables, not for proc-local ones;
It wraps around variable;
[namespace exists ...]: These guards protect from operations when a given parent namespace is currently under deletion (namespace delete ::ns1, or child interp deletion);
In the unset case, the handler script re-adds the trace on the well re-created variable (otherwise, any subsequent write would not be caught anymore.);
[trace info variable ...]: Helps avoid adding redundant traces;
[namespace which -variable]: Makes sure to work on a fully-qualified variable name;
Some final remarks:
Ooo, maybe I can substitute the normal unset for a custom version and
do the checking in it instead of relying on trace
Certainly one option, but it does not give you coverage of the various (indirect) paths of unsetting a variable.
[...] in a safe interp.
You may want to interp alias between a variable in your safe interp to the above readOnly in the parent interp?
I have process were variables are defined, and following that procedure the variables should be used after a delay.
The problem is that the delayed command process the variables when the command is executed instead of when the command is given. Consider the following example:
The code is not tested, but the point should be clear anyway:
for {set i 0} {$i < 100} {incr i} {
set outputItem $i
set time [expr 1000+100*$i]
after $time {puts "Output was $outputItem"}
}
Which I would hope print something like:
Output was 1
Output was 2
Output was 3
...
But actually it prints:
Output was 100
Output was 100
Output was 100
Which I guess shows that tcl keeps the parameter name (and not the value of the variable) when the after command is initiated.
Is there any way to substitute the variable name to the variable content, so that the delayed command (after xxx yyy) works as desired?
The problem is this line:
after $time {puts "Output was $outputItem"}
The substitution of $outputItem is happening when the after event fires, not at the time you defined it. (The braces prevent anything else.) To get what you want, you need list quoting, and that's done with the list command:
after $time [list puts "Output was $outputItem"]
The list command builds lists… and pre-substituted commands (because of the way Tcl's syntax is defined). It's great for building things that you're going to call later. I guess it could have been called make-me-a-callback too, but then people would have wondered about its use for creating lists. It does both.
If your callback needs to be two or more commands, use a helper procedure (or an apply) to wrap it up into a single command; the reduction in confusion at trying to make callbacks work with multiple direct commands is totally worth it.
I don't really understand how to use the eval arguments.
If the script I want to eval is :
set myscript { puts $::argv }
Then I want to call my script like this :
eval $myscript anArgument
And I expect the output to be "anArgument", but instead I have :
can not find channel named ""
while evaluating {eval $script vvv}
When you invoke eval, the command concatenates all its arguments and attempts to evaluate the resulting list (or string if you will, it's the same thing here). So, first the arguments { puts $::argv } and anArgument are concatenated into the list {puts $::argv anArgument}, and then the interpreter tries to evaluate that. If the value of the global variable argv is an empty list, the actual command invoked will be equivalent to puts {} anArgument. puts will try to use {} as a channel identifier to output to, fail and leave an error message.
Now, if what you wanted to do was to pass anArgument to myscript and then eval it as puts anArgument, you should instead write
set myscript {puts $myarg}
set myarg anArgument
eval $myscript
In the first line, the evaluation of $myarg is postponed because of the quoting braces which turn $ into a regular text character. The variable myarg is then set to a value (this can happen anywhere in the code as long as it comes before the eval). In the third line, the script is evaluated, and at that point the argument $myarg is replaced by the value anArgument, which is then printed.
The sort of invocation you attempted is possible, but then you need to use apply instead of eval, and a closure (anonymous function) instead of a script.
set myfunc {myarg {puts $myarg}}
apply $myfunc anArgument
The global variable argv does not pass arguments to a script which is passed to eval: when tclsh or wish is started by the operating system, any command line arguments given are placed in argv, and the value is never changed during execution unless you change it yourself (don't do that, it's just confusing).
Documentation: apply, eval, puts, set
argv is documented here.
So basically in my tcl script I generate a line of tcl code that I have to execute( ie I come up with this instruction during runtime). Once I have the tcl instruction say for example puts "hello world $test_var". How do I execute this using tcl?
Do I use the eval command or what?
The eval command is a reasonable way of doing this, but you might want to consider using catch instead, since that will trap any problems found during the evaluation of the generated code, for example like this:
# Generate the code somehow
set variable {puts "hello word $test_var"}
# Execute and trap problems
if {[catch $variable resultMsg]} {
# An error (or other exception) happened
puts stderr "BOOM, Error! $resultMsg"
}
Instead of using [eval] which works perfectly well, in newer versions of Tcl you can use the {*} syntax.
For example:
set cmd "puts"
lappend cmd "abcd ef"
{*}$cmd
Note that it's a good habit to use list commands to build up your command so you wont run into quoting issues.
I'm afraid you don't have another reasonable choice than to use the eval command, e.g.:
set variable {puts "hello world $test_var"}
eval $variable