I would like to have a procedure with a single argument that either executes the argument using uplevel 1 (as if no procedure call) or print the argument using uplevel 1 (to have the right context for variable substitutions).
proc exec_or_print {cmd} {
if 0 {
uplevel 1 $cmd
} else {
uplevel 1 puts $cmd
}
}
set a "temp"
exec_or_print {
puts "something $a"
}
results in
can not find channel named "puts"
while executing
"puts puts "something $a""
uplevel 1 [list puts $cmd]
prints puts "something $a"
, which is missing the var substitution.
uplevel 1 puts {$cmd}
prints ::yaml::_setAnchor
, which is missing the procedure var substitution
uplevel 1 puts \"$cmd\"
results in
extra characters after close-quote
while executing
"puts "
What would be the right syntax to express the intended functionality?
The uplevel
command, when presented with multiple non-level arguments, will concat
those arguments to form the script to call in the other stack level. This is often not what you want, but is retained for backward compatibility. (The more-modern tailcall
command does not work that way.)
The first-level workaround is to use list
to built the script to pass to uplevel
when there are several arguments otherwise:
proc exec_or_print {cmd} {
if 0 {
uplevel 1 $cmd
} else {
uplevel 1 [list puts $cmd]
}
}
This doesn't show the values that would be substituted though. In that case, you need to call subst
in the correct context, and that can have non-trivial side effects (because it can call command substitutions and variables could be traced):
proc exec_or_print {cmd} {
if 0 {
uplevel 1 $cmd
} else {
puts [uplevel 1 [list subst $cmd]]
}
}
The puts
itself can be in the procedure; it doesn't care what stack frame it is called from.