Tcl Part I · Module 2 · The Tcl Execution Model
Substitution Happens Once — and Why eval Exists
This chapter closes the module by answering the question the rest of it leaves open. Tcl performs one round of substitution and then runs the command, and what comes back from a substitution is a value rather than more Tcl. That is what makes Tcl predictable. But occasionally you really do have text that should be run as a command, and Tcl has to provide a way to say so. That way is eval, and most of the time reaching for it is a sign that something simpler would be better — so this chapter teaches the simpler thing too.
Intermediate12 min readTclPart IExecution ModelevalSubstitution
Part I · Module 2 · Chapter 2.11 · Substitution Happens Once — and Why eval Exists
1. The Rule, Restated
Chapter 2.2 established it and proved it three ways:
Tcl performs one round of substitution, then runs the command. What a substitution returns is a value, not more Tcl.
Everything in this module has been a consequence of that. This chapter deals with the one case where you want something else.
2. What the Rule Costs You
Suppose you build a command as text:
set cmd "puts hello"
$cmdinvalid command name "puts hello"Read the error carefully — it is doing exactly what the rule says.
$cmd was substituted once, producing the eleven characters puts hello. That became one word. The first word of a command is the command name, so Tcl looked for a command called puts hello, spaces and all. There is no such command.
Tcl did not split the value into two words, because Chapter 2.2 says substitution never changes word boundaries. The rule held. It just was not what you wanted.
3. eval: Asking for a Second Round
eval takes text and runs it as Tcl:
set cmd "puts hello"
eval $cmdhelloNow it works. eval received the text puts hello and evaluated it as a script — which means a fresh round of parsing, word splitting and substitution.
So the second round exists. It is just not automatic. You have to name a command that does it, and that is the whole design: nothing is re-evaluated unless something was explicitly asked to evaluate it.
4. The Better Answer for the Common Case
Most of the time, reaching for eval means you have a list of arguments and want them passed as separate words. That is not a second round of substitution — it is a word-splitting problem, and Tcl 8.5 added a direct way to say it.
{*} expands a value into separate words:
proc show {args} { puts "received [llength $args] word(s)" }
set pair "alpha beta"
show $pair
show {*}$pairreceived 1 word(s)
received 2 word(s)That is the Chapter 2.2 §8 exception, used on purpose.
Compare the two approaches for building a call:
set args "hello"
eval "puts $args" ;# builds text, then re-parses all of it
puts {*}$args ;# passes the words directlyhello
helloSame output, different risk. The first rebuilds a command as text and re-parses everything in it. The second never leaves the world of values.
5. eval Re-Parses Everything
The risk is easy to state. A second round of parsing applies to the whole text, not just the part you were thinking about:
set value "a b c"
eval "puts [list $value]"That still works, but notice what you had to do to make it safe — wrap the value so the second parse sees one word. Every value that goes into an eval needs that kind of thought, and forgetting it on one of them is enough.
With {*} there is no second parse, so there is nothing to protect against.
6. Why eval on Untrusted Text Is a Defect
Now the case that matters beyond style. Suppose text comes from outside your script — a file, an argument, a user:
set userinput {world; puts "INJECTED COMMAND RAN"}
eval "puts $userinput"world
INJECTED COMMAND RANThe second line was never part of your program. It arrived as data and eval ran it, because a semicolon ends a command — Chapter 2.10 — and the second round of parsing honoured it.
Without eval, the same value is harmless:
set userinput {world; puts "INJECTED COMMAND RAN"}
puts $userinputworld; puts "INJECTED COMMAND RAN"One word, printed as text. Nothing ran.
7. Common Mistakes
Using eval to pass arguments. §4. {*} is the direct way.
Building a command string by concatenating values. Each value needs protecting against the second parse. Prefer building a list and expanding it.
Expecting $cmd alone to run. §2. It names a command, it does not run a script.
Assuming braces make eval safe. They stop substitution when the word is built. eval then parses the text anyway — that is its job.
Reaching for eval early. In Part I you will not need it. If a line seems to require it, there is usually a command that does the job directly.
8. Exercises
Exercise 1 — read the error. Run set cmd "puts hello" then $cmd. Explain the error in terms of word count. How many words did Tcl think the command had?
Exercise 2 — three ways. Make hello print, using: (a) eval, (b) {*}, (c) neither. Which would you keep?
Exercise 3 — see the injection. Run the injection.tcl example from §6, then the safe.tcl one. Confirm the second prints the semicolon as text. Write one sentence explaining the difference to a colleague.
Exercise 4 — count the words. Predict, then check:
proc show {args} { puts "received [llength $args] word(s)" }
set three "a b c"
show $three
show {*}$three
show "{*}$three"The third one is a trap. Explain it using Chapter 2.3 §4.
Exercise 5 — find the simpler way. This uses eval for no reason:
set message "hello"
eval "puts $message"Rewrite it without eval, two different ways, and confirm both print hello. Then change the value to "one two" and run all three versions again. Two of them now fail — explain why, using Chapter 1.7 §3.
9. What You Can Now Do
That closes the module. Look back at what it established, because it is more than it felt like at the time.
You can take a line of Tcl and say:
- where the command ends
- how many words it has
- which characters are grouping instructions and which are data
- which substitutions will run, and which will not
- what the final values will be
- what the command will actually receive
That is the whole of Tcl's execution model. There is no second system to learn later. The same rules govern a one-line script and a ten-thousand-line one, and — as Chapter 1.3 promised — they are unchanged when Tcl is embedded inside another application. An application adds commands; it does not add grammar.
10. Remember
- One round of substitution, then the command runs. That is the default and it never changes.
- A substituted value is data, not Tcl. It is not re-read.
evalasks for a second round by taking text and running it as a script.{*}expands a value into separate words without a second parse, and is usually what you actually wanted.evalre-parses everything it is given, so every value inside needs protecting.- Never pass text you did not write into
eval. A semicolon in that text starts a new command. - Nothing in Tcl is re-evaluated unless you name a command that evaluates it.
Module 3 starts on values themselves — what set really does, how Tcl decides whether something is a number, and why arithmetic is a command rather than syntax. You now have the model that makes those answers make sense.
Where this fits
Part of the Tcl curriculum.
