Skip to content
VLSI Mentor

Tcl Part I · Module 13 · Regular Expressions

Why a Regex Belongs in Braces

Every pattern in this module has been written in braces, with a promise that the reason would come. Here it is, and it is worth the wait: a regular expression written in double quotes is not the pattern you typed. Tcl processes backslashes and dollar signs in a quoted word before the command ever runs, so the regex engine receives something shorter and different — and because the result is usually still a valid pattern, nothing fails. It simply matches the wrong thing, quietly, on inputs you did not think to test.

Intermediate11 min readTclPart IRegexQuotingBraces

Part I · Module 13 · Chapter 13.5 · Why a Regex Belongs in Braces

1. The rule

Put every regular expression in braces.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
regexp {(\d+)} $line -> n      ;# correct
regexp "(\d+)" $line -> n      ;# broken, and silent about it

The rest of the chapter is what the second line actually does.

2. What Tcl does to the pattern first

A quoted word goes through backslash substitution (Chapter 2.6) while the line is being parsed. \d is not a recognised escape, so Tcl reduces it to plain d:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "braced  : '[set p {(\d+)}]'  ([string length {(\d+)}] characters)"
puts "quoted  : '[set q "(\d+)"]'  ([string length "(\d+)"] characters)"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
braced  : '(\d+)'  (5 characters)
quoted  : '(d+)'  (4 characters)

Five characters against four. The backslash is gone before regexp is called, so the engine is handed (d+) — a perfectly valid pattern meaning "one or more letter d".

3. What that does to the match

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set line "width 32"

regexp {(\d+)} $line -> braced
regexp "(\d+)" $line -> quoted

puts "braced pattern captured : '$braced'"
puts "quoted pattern captured : '$quoted'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
braced pattern captured : '32'
quoted pattern captured : 'd'

The braced version found the number. The quoted version found the letter d in the word "width" — and reported success, because it did match something.

4. Every escape, not just \d

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach e {{\d} {\s} {\w} {\.} {\n} {\t}} {
    set quoted [subst -nocommands -novariables $e]
    puts [format "  %-4s braced=%d chars   after quoting=%d chars   %s" \
        $e [string length $e] [string length $quoted] \
        [expr {$e eq $quoted ? "unchanged" : "CHANGED"}]]
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  \d   braced=2 chars   after quoting=1 chars   CHANGED
  \s   braced=2 chars   after quoting=1 chars   CHANGED
  \w   braced=2 chars   after quoting=1 chars   CHANGED
  \.   braced=2 chars   after quoting=1 chars   CHANGED
  \n   braced=2 chars   after quoting=1 chars   CHANGED
  \t   braced=2 chars   after quoting=1 chars   CHANGED

All of them collapse. \d \s \w become bare letters; \. becomes an unescaped dot that now matches any character (Chapter 13.3 §3); \n and \t become real newline and tab characters, which the regex engine then treats as literal whitespace rather than as the escapes you meant.

5. The dollar sign, inconsistently

$ is the end-of-string anchor in a regex and the variable prefix in Tcl, so a quoted pattern containing one may or may not survive:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set end "SUBSTITUTED"

puts "quoted \"x\$\"     : '[set a "x$"]'"
puts "quoted \"x\$end\"  : '[set b "x$end"]'"
puts "braced {x\$end}  : '[set c {x$end}]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
quoted "x$"     : 'x$'
quoted "x$end"  : 'xSUBSTITUTED'
braced {x$end}  : 'x$end'

A lone $ at the very end survives, because there is no name after it to substitute. A $ followed by letters does not — the anchor is replaced by the contents of a variable, or raises can't read "end": no such variable if none exists.

6. This is the same lesson, a third time

You have met this twice already:

  • Chapter 8.2 — an unbraced if condition is substituted early, and can execute data as code.
  • Chapter 9.7 — a quoted while condition is frozen at parse time, so the loop never ends.

All three are one rule: braces mean "evaluate this later, by the thing that understands it". For if the later evaluator is expr; for while it is expr again, each pass; here it is the regex engine. Quotes mean "evaluate this now, by Tcl", and Tcl does not understand regular expressions.

7. When you genuinely need to build a pattern

Sometimes part of a pattern comes from a variable. The answer is not to abandon braces — it is to build the pattern as a value and pass that:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set keyword "width"

# wrong: the whole pattern is quoted, so \d and \y are eaten too
set bad "^\y$keyword\y\\s*=\\s*(\d+)$"

# right: braced fragments, joined
set good "^\\y${keyword}\\y\\s*=\\s*(\\d+)\$"

# clearer still: keep the fixed part braced and interpolate narrowly
set pattern [format {^\y%s\y\s*=\s*(\d+)$} $keyword]

puts "pattern : $pattern"
foreach line {"width = 32" "max_width = 8" "width=7"} {
    if {[regexp $pattern $line -> n]} {
        puts "  '$line' -> $n"
    } else {
        puts "  '$line' -> no match"
    }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
pattern : ^\ywidth\y\s*=\s*(\d+)$
  'width = 32' -> 32
  'max_width = 8' -> no match
  'width=7' -> 7

format with a braced template is the readable way: the pattern's own backslashes are protected by the braces, and only the %s is substituted. The \y word boundaries from Chapter 13.3 §5 are what reject max_width.

8. A worked example

The same parser written both ways, run on the same input:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set lines {"width = 32" "depth = 16" "mode = fast"}

puts "braced patterns:"
foreach line $lines {
    if {[regexp {^(\w+)\s*=\s*(\d+)$} $line -> k v]} {
        puts "  $k -> $v"
    } else {
        puts "  $line -> not a numeric setting"
    }
}

puts "quoted patterns:"
foreach line $lines {
    if {[regexp "^(\w+)\s*=\s*(\d+)$" $line -> k v]} {
        puts "  $k -> $v"
    } else {
        puts "  $line -> not a numeric setting"
    }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
braced patterns:
  width -> 32
  depth -> 16
  mode = fast -> not a numeric setting
quoted patterns:
  width = 32 -> not a numeric setting
  depth = 16 -> not a numeric setting
  mode = fast -> not a numeric setting

The braced version is correct: two numeric settings parsed, one rejected. The quoted version rejected everything — because \w became w, \s became s and \d became d, leaving a pattern that looks for a literal w followed by s*=s* and digits spelled d.

That is the lucky outcome. A pattern that matches nothing is at least visible. Section 3's example matched the wrong thing, which is the version that reaches production.

9. Exercises

Exercise 1. How many characters is {(\d+)}, and how many is "(\d+)" after Tcl has processed it?

Exercise 2. What does regexp "(\d+)" "width 32" -> n put in n, and why is that worse than an error?

Exercise 3. What happens to \. inside double quotes, and what does the resulting pattern then match?

Exercise 4. Why does "(\d+)$" appear to work while "^(\w+)$sep" does not?

Exercise 5. Name the two earlier chapters that taught the same underlying rule.

Exercise 6. Write a pattern matching KEY = <digits> where KEY comes from a variable, keeping the pattern's backslashes intact.

Answers

1. Five characters braced — (, \, d, +, ). Four after quoting — (, d, +, ) — because Tcl reduces the unrecognised escape \d to d.

2. d — the letter in "width". It is worse than an error because regexp returns 1, a variable is set to a plausible-looking value, and the script continues with wrong data and nothing to search for.

3. It becomes a bare ., which in a regex matches any character rather than a literal dot. So a pattern meant to match .csv will also match Xcsv.

4. Because a $ with nothing after it is not a variable reference, so Tcl leaves it alone and the anchor survives. A $ followed by letters is a variable reference and gets substituted, replacing the anchor with the variable's contents — or raising if it does not exist.

5. Chapter 8.2, on if conditions, and Chapter 9.7, on loop conditions. All three are "braces mean evaluate later, by the thing that understands it".

6.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set key "timeout"
set pattern [format {^\y%s\y\s*=\s*(\d+)$} $key]
puts $pattern
puts [regexp $pattern "timeout = 300" -> n]
puts $n
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
^\ytimeout\y\s*=\s*(\d+)$
1
300

10. Remember

  • Put every regular expression in braces. No exceptions worth remembering.
  • A quoted pattern is rewritten before regexp runs — {(\d+)} is five characters, "(\d+)" arrives as four.
  • \d, \s, \w, \., \n, \t all collapse under Tcl's backslash substitution.
  • The failure is silent and often still matches — "(\d+)" on width 32 captures the letter d.
  • $ survives at the very end of a quoted pattern and not elsewhere, which makes quoting look safe until it is not.
  • This is Chapter 8.2 and Chapter 9.7's rule again — braces mean "evaluate later, by the thing that understands it".
  • To build a pattern with a variable in it, use format with a braced template, so only the inserted part is substituted.
  • An inserted fragment is not escaped — if it comes from outside your script, its punctuation becomes pattern syntax.

Where this fits

Part of the Tcl curriculum.