The “problem” with expr
is that it implements its own “mini language”, which includes, among other things, variable substitution (replacing those $a
-s with their values) and command substitution (replacing those [command ...]
things with the results of running command
s), so basically the process of evaluating expr $a + $b
goes like this:
- The Tcl interpreter parses out four words —
expr
,$a
,+
and$b
out of the source string. Since two of these words begin with$
, variable substitution takes place so really there will beexpr
,1
,+
, and2
. - As usually, the first word is taken to be the name of a command, and others are arguments to it, so the Tcl interpreter looks up a command named
expr
, and executes it passing it the three arguments:1
,+
, and2
. - The implementation if
expr
then concatenates all the arguments passed to it interpreting them as strings, obtaining a string1 + 2
. - This string is then parsed again — this time by the
expr
machinery, according to its own rules which include variable- and command substitutions, as already mentioned.
What follows:
- If you brace your
expr
essions, like inexpr {$a + $b}
, grouping provided by those curly braces inhibits interpretation by the Tcl interpreter1 of the script intended to be parsed byexpr
itself. This means in our toy example theexpr
command would see exactly one argument,$a + $b
, and will perform substitutions itself. -
“Double parsing” explained above might lead to security problems.
For example, in the following code
set a {[exec echo rm -rf $::env(HOME)]} set b 2 expr $a + $b
The
expr
command will itself parse a string[exec echo rm -rf $::env(HOME)] + 2
. Its evaluation will fail, but by that time, the contents of your home directory will be supposedly gone. (Note that a kind Tcler placedecho
in front ofrm
in a later edit to my answer in an attempt to save the necks of random copypasters, so the command as written won’t callrm
but if you removeecho
from it, it will.) - Double parsing inhibits certain optimisations the Tcl engine can do when dealing with calls to
expr
.
1 Well, almost — “backslash+newline” sequences are still processed even inside {...}
blocks.