-Q9P- get quoted strings to work the way I want?

From: -X-  How, in Tcl, can I XXX:

A long article dealing with the issues can be found at
ftp://harbor.ecn.purdue.edu/pub/tcl/docs/README.programmer

Here are some short answers:

Q. I'm trying to build up a command for later execution but am
having trouble with variable values that include whitespace
or special characters.

A. The safest way to build up commands is to use the list command
so that you can keep track of the list structure.  Avoid using
double quotes because you can end up with an extra trip through
the evaluator.  We'll illustrate this with a command to create
a button that prints out the label on the button when you click it.
    Wrong answer #1:
	button $myname -text $label -command "puts stdout $label"
    Why? because if $label has whitespace then the puts command will
	be passed the wrong number of arguments.  If $label has $ or [ ]
	characters, they will be interpreted instead of printed.
    Good answer #2:
	button $myname -text $label -command [list puts stdout $label]
    Why? because list will properly quote the value of $label

Q. I'm trying to build up a command for later execution but am
having trouble getting some variables to evaluate now, and some
to evaluate later when the command is run.

A. The cleanest way to do this is to define a procedure that hides
the use of the variables at run time, and then build up a call to
that procedure using the list command as described previously.  (You
can even define the procedure on the fly.  It will have global scope
even it if is created within another procedure.)
    Wrong answer #1:
	button $myname -text $label -command \
	    [list puts stdout $ArrayOfDynamicStuff($label)]
    Why? The array value will be substituted when the button is created,
	not later on when the button is clicked.  Also, note that the
	command is executed at the global scope, so it is not necessary
	to include a "global ArrayOfDynamicStuff" in the command.
    Wrong answer #2 (backquotes and list):
	button $myname -text $label -command \
	    [list puts stdout \$ArrayOfDynamicStuff($label)]
    Why? Here the list command and the backquote of $ are fighting with
	each other.  The command ends up being something like:
	    puts stdout {$ArrayOfDynamicStuff(foo)}
	which prevents the substitution of the value of the array element.
    Dubious answer #3 (backquotes and double-quotes):
	button $myname -text $label -command \
	    "puts stdout \$ArrayOfDynamicStuff($label)"
    Why? This only works if the value of $label has no special characters
	or whitespace.
    Clean answer #4 (proc):
	proc doit { i } {
	    global ArrayOfDynamicStuff
	    puts stdout $ArrayOfDynamicStuff($i)
	}
	button $myname -text $label -command [list doit $label]
    Why? Using little TCL procs for your button commands is a good habit
	because it eliminates most needs for fancy quoting, and it
	makes it easier to tweak the button command later on.

Q. I'm trying to pass along a variable number of args to another procedure
but I'm having trouble getting the $args to expand right.

A. Avoid using eval and double quotes because that results in
an extra trip through the interpreter.  The eval command will do
a concat of its arguments if there are more than one, so that
pretty much eliminates the need to group things with double quotes.
Let's extend the button example:
    Wrong answer #1:
	proc mybutton { myname label args } {
	    button $myname -text $label -command [list puts stdout $label] $args
	}
    Why? All the extra arguments to mybutton are grouped into one list element
	that is but into the value of $args.  However, the button command
	expects to see individual arguments, not a sub-list.
    Wrong answer #2:
	proc mybutton { myname label args } {
	    eval "button $myname -text $label -command [list puts stdout $label] $args"
	}
    Why? The double quotes allow expansion of $label as well as $args, so if
	$label has any whitespace, the button command will be malformed
    Good answer #3:
	proc mybutton { myname label args } {
	    set cmd {button $myname -text $label -command [list puts stdout $label]}
	    eval $cmd $args
	}
    Why? Eval will first concatenate its two arguments and then run the
	result through the interpreter.  Think of this as stripping off the
	outer curly braces from $cmd and $arg and making a single list
	with all the elements of both.  $label will be evaluated exactly
	once, so the puts command will remain good, and whatever went into
	args will also be processed exactly one time.

Q. Why do I get a syntax error in an if/while/for statement?
A. You may have written something like
	wish: set foo bar
	wish: if {$foo == bar} {puts stdout bar}
	syntax error in expression "$foo == bar"

in which bar is interpreted as neither a string nor a variable, since
strings as operands in expressions MUST be surrounded by double quotes
or braces. 

Change to
	wish: if {$foo == "bar"} {puts stdout bar}
or
	wish: if {$foo == {bar}} {puts stdout bar}

always in expressions, depending on if you want expansion performed or
not. 

Contributed by "Jesper Blommaskog" <d9jesper@dtek.chalmers.se>
Go Back Up

Go To Previous

Go To Next