GPS: I'm sick of the discussion over the expand vs. {} vs. {*} and so on, so I came up with this. Comments are welcome...
GPS: To help the reader understand this (after a comment in Tcl'ers chat). The ~ token is discarded in a safe.eval call. ~ is like the proposed ` except it must be preceded and followed by whitespace. Any list or string after a ~ token is expanded. Simple no mess solution to a simple problem. The character is easily changed if you like.
I have added tests to demonstrate how it solves the problems of whitespace in widget pathnames, and potentially dangerous commands being executed.
GPS: Sep 2, 2003 -- I have improved version 5 of safe.eval in several ways. Rather than having to use two tildes like: ~SPACE~SPACE to pass a single tilde you may now use ~~. This fits in nicely with other tools that react to %% as meaning a literal %. I have left version 4 because the tests are pretty much the same, and I thought someone might find the difference interesting.
#Copyright 2003 George Peter Staplin
#You may use this under the same terms as Tcl.
#Thanks to RS and AM for comments via the Tcl'ers Chat.
#rev 5
proc safe.eval args {
set cmd [list]
set expand 0
foreach arg $args {
if {"~~" == $arg} {
lappend cmd ~
set expand 0
} elseif {"~" == $arg} {
set expand 1
} elseif {$expand} {
foreach a $arg {
lappend cmd $a
}
set expand 0
} else {
lappend cmd $arg
set expand 0
}
}
uplevel 1 $cmd
} #Copyright 2003 George Peter Staplin
#You may use this under the same terms as Tcl.
#Thanks to RS and AM for comments via the Tcl'ers Chat.
#rev 4
proc safe.eval args {
set expand 0
set cmd [list]
foreach arg $args {
if {"~" == $arg} {
if {$expand} {
#we have ~ ~
lappend cmd ~
set expand 0
continue
}
set expand 1
} else {
if {$expand} {
set cmd [concat $cmd $arg]
} else {
lappend cmd $arg
}
set expand 0
}
}
uplevel 1 $cmd
}
proc A {a b c} {
puts A:
foreach v [list a b c] {
puts " $v: [set $v]"
}
}
proc B {a b c d} {
puts B:
foreach v [list a b c d] {
puts " $v: [set $v]"
}
}
proc C {a b c} {
puts C:
foreach v [list a b c] {
puts " $v: [set $v]"
}
}
proc main {} {
#Let's do some simple expansion
#A expects 3 arguments
safe.eval A ~ [list 1 2 3]
#Now let's use a potentially dangerous command
set l [list hello world bye]
#We want $l to expand, but not [dangerous]
#B expects 4 arguments
safe.eval B {[dangerous]} ~ $l
set l [list hi world]
#exec shouldn't be called
safe.eval C ~ $l "\[exec\]"
puts "Now testing with a typical Tk usage..."
package require Tk
button .b
button .b2
button .b3
puts BEFORE:[winfo children .]
safe.eval destroy ~ [winfo children .]
puts AFTER:[winfo children .]
#Now for a commonly complained about issue...
#The case of a window pathname with a space in it.
#In the normal eval this would be clobbered, but not with safe.eval.
set flags "-text Exit -command exit "
append flags "-bg orange -fg yellow"
set win ".b ob"
safe.eval button $win ~ $flags -bd 0
pack $win
}
main