Reputation: 1
How can I protect a variable when I'm sourcing a script that using the same variable name. That is, how can I keep x value in the return from the source even if it changed in the script.
example
Here is the main code that sourcing script.tcl:
set list {1 2 3 4 5}
set x 1
source $script.tcl
script.tcl:
# some script with loop on x:
foreach x $list {}
The problem is that x equal some value (in this case "1") before the source of the file, but after the source operation, x is the last iteration value from the list.
I'd like to preserve the value of x (and I don't want to change the names).
Upvotes: 0
Views: 879
Reputation: 13252
Just for completeness: a namespace can also be used to contain the source operation and control which variables it accesses.
namespace eval TEMP {variable x}
# ... stuff ...
namespace eval TEMP {source script.tcl}
A single namespace eval
invocation can be used, or separate ones as desired.
There are some subtleties. When writing to a variable inside a namespace eval
(outside of a procedure scope, that is), the following priority order is used:
(Names can exist by having a value or by appearing in a variable
command invocation.)
The order for reading a variable is
This means that you can "leak in" variables from the global namespace into the current by avoiding using variable
on them. In this example, the value of the list
variable is accessed that way. By making sure there is an existing name in the current namespace, the global name will be shadowed and protected from reading or writing.
You can similarly "leak out" variables by making sure there is a global name for them (and again avoiding using variable
).
For instance, if the script in script.tcl
is
# some script with loop on x:
foreach x $list {incr n $x}
and you source it like
set n 0
namespace eval TEMP {source script.tcl}
then
set n
# => 15
Without the set n 0
, the name n
is instead created as ::TEMP::n
.
Upvotes: 0
Reputation: 137587
In general, if you've got one script sourcing another script where that other script isn't well behaved (being well behaved would involve the script doing everything in a namespace and generally being careful), your best bet is to source
that script in a child interpreter.
# Here is our protected variable
set x "a very important value"
# Make the child
interp create child
# Set it up to be ready
child eval [list set list {1 2 3 4 5}]
# Run the script
catch {
child eval [list source script.tcl]
}
# Possibly get things from that interpreter here
# Dispose of the interpreter
interp delete child
# Show that the variable is OK
puts $x
There's absolutely nothing shared between the interpreters other than commands you create as shared. Those shared commands are aliases, and let you provide whatever sort of profiled extension commands you want. Variables are not shared at all.
For truly untrusted other scripts, you can use a safe interpreter; those are child interpreters with unsafe commands (including source
and everything else that touches the filesystem) removed so that there's much less that a script can do to fool with you. They might be overkill in this instance.
Upvotes: 3
Reputation: 3434
Use apply
to evaluate the source script in a dedicated environment, without "polluting" the sourcing context:
apply [list {list} [list source $script.tcl]] $list
That said, it is not entirely clear how data or state from one script should be communicated to the other, if at all? Using apply
, one would have to a rely on the return value from source $script.tcl
or some scoped variables from within the apply
lambda script (e.g., implicitly via global
or explicitly via fully-qualified variable names: set ::x 1
).
Upvotes: 0
Reputation: 246837
To demonstrate the problem:
% set list {1 2 3 4 5}
1 2 3 4 5
% set x 1
1
% source script.tcl
% set x
5
You could source the script in a proc to introduce a new variable scope
% proc do_script {} {
upvar 1 list list ;# make $list available in this scope
source script.tcl
puts "in proc, x=$x"
}
% set x 1
1
% do_script
in proc, x=5
% set x
1
Upvotes: 0