Skip to content
VLSI Mentor

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:

Azvya Education Pvt. Ltd.VLSI Mentor
as-text.tcl
set cmd "puts hello"
$cmd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
invalid 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:

Azvya Education Pvt. Ltd.VLSI Mentor
with-eval.tcl
set cmd "puts hello"
eval $cmd
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
hello

Now 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:

Azvya Education Pvt. Ltd.VLSI Mentor
expand.tcl
proc show {args} { puts "received [llength $args] word(s)" }

set pair "alpha beta"

show $pair
show {*}$pair
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
received 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:

Azvya Education Pvt. Ltd.VLSI Mentor
two-ways.tcl
set args "hello"

eval "puts $args"      ;# builds text, then re-parses all of it
puts {*}$args          ;# passes the words directly
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
hello
hello

Same 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:

Azvya Education Pvt. Ltd.VLSI Mentor
whole-text.tcl
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:

Azvya Education Pvt. Ltd.VLSI Mentor
injection.tcl
set userinput {world; puts "INJECTED COMMAND RAN"}

eval "puts $userinput"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
world
INJECTED COMMAND RAN

The 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:

Azvya Education Pvt. Ltd.VLSI Mentor
safe.tcl
set userinput {world; puts "INJECTED COMMAND RAN"}

puts $userinput
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
world; 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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
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.
  • eval asks 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.
  • eval re-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.