Skip to content
VLSI Mentor

Tcl Part I · Module 13 · Regular Expressions

regsub: Substituting by Pattern

Matching finds things; substituting changes them. regsub takes a pattern, a subject, a replacement and the name of a variable to put the result in — and the detail worth fixing in your head immediately is that last clause, because the command's return value is not the new string. It is a count of how many substitutions were made. That is the opposite shape to regexp, which returns the thing you usually want, and mixing the two up produces code that assigns a number where text belongs.

Intermediate11 min readTclPart IRegexregsubText processing

Part I · Module 13 · Chapter 13.7 · regsub: Substituting by Pattern

1. The shape

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
regsub {\s+} "a   b  c" "_" result
puts "result : $result"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
result : a_b  c

Four arguments: pattern, subject, replacement, and the name of a variable for the result. Only the first run of spaces was replaced, because regsub stops after one substitution by default.

-all replaces every match:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
regsub -all {\s+} "a   b  c" " " squeezed
puts "squeezed : '$squeezed'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
squeezed : 'a b c'

Collapsing runs of whitespace is probably the single most common use.

2. The return value is a count

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set n [regsub -all {\s+} "a   b  c" " " squeezed]

puts "returned : $n"
puts "result   : '$squeezed'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
returned : 2
result   : 'a b c'

Two substitutions were made. The result went into squeezed; the count came back from the command.

3. Omitting the variable returns the result

Tcl offers the form people usually expect — leave out the variable name and the new string comes back:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts [regsub -all {\d} "a1b2" "X"]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
aXbX

That is often the clearest spelling, and it composes:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set line "  padded   text   here  "
puts "'[string trim [regsub -all {\s+} $line " "]]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'padded text here'

4. A failed substitution still writes the variable

Unlike regexp, which leaves capture variables untouched on failure (Chapter 13.4 §5), regsub always produces a result:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set out "PREVIOUS"
set n [regsub {zzz} "abc" "X" out]

puts "returned : $n"
puts "out      : '$out'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
returned : 0
out      : 'abc'

Zero substitutions, and out holds the unchanged input rather than its old value. That is the sensible behaviour — "the result of replacing nothing" is the original — and it means you never have to guard a read the way you must with regexp.

5. & is the whole match

In the replacement, & stands for whatever the pattern matched:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts [regsub {\d+} "abc123" {[&]}]
puts [regsub -all {\w+} "one two" {<&>}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
abc[123]
<one> <two>

To put a literal ampersand in the replacement, escape it:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts [regsub {\d+} "abc123" {\&}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
abc&

6. Backreferences reuse the groups

\1, \2 and so on stand for the capture groups, numbered as in Chapter 13.4 §7:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts [regsub {(\w+)@(\w+)} "user@host" {\2:\1}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
host:user

The two halves were swapped. A more practical version — reformatting a date:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set dates "2026-01-05 and 2025-12-31"
puts [regsub -all {(\d{4})-(\d{2})-(\d{2})} $dates {\3/\2/\1}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
05/01/2026 and 31/12/2025

7. What regsub is genuinely good at

Normalising whitespace:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
puts "'[string trim [regsub -all {\s+} "  a   b  " " "]]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'a b'

Stripping comments:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
foreach line {"width = 32   # the bus width" "mode = fast" "# whole line"} {
    set clean [string trim [regsub {\s*#.*$} $line ""]]
    puts "  '[set line]' -> '$clean'"
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  'width = 32   # the bus width' -> 'width = 32'
  'mode = fast' -> 'mode = fast'
  '# whole line' -> ''

Counting as a side effect, which is the one time the return value is what you want:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set text "a1 b2 c3 d"
set n [regsub -all {\d} $text "" stripped]
puts "removed $n digit(s), leaving '$stripped'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
removed 3 digit(s), leaving 'a b c d'

8. A worked example

Cleaning up a configuration file in one pass:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
proc tidy {line} {
    # strip a trailing comment
    set line [regsub {\s*#.*$} $line ""]
    # collapse internal whitespace
    set line [regsub -all {\s+} $line " "]
    # normalise spacing around the equals sign
    set line [regsub {\s*=\s*} $line " = "]
    return [string trim $line]
}

set lines {
    "width=32"
    "  mode   =   fast   # the run mode"
    "depth = 16"
    "# a whole-line comment"
    "   "
}

foreach l $lines {
    set t [tidy $l]
    if {$t eq ""} {
        puts [format "  %-32s (dropped)" "'$l'"]
    } else {
        puts [format "  %-32s -> '%s'" "'$l'" $t]
    }
}
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
  'width=32'                       -> 'width = 32'
  '  mode   =   fast   # the run mode' -> 'mode = fast'
  'depth = 16'                     -> 'depth = 16'
  '# a whole-line comment'         (dropped)
  '   '                            (dropped)

Three substitutions in sequence, each doing one job, each using the no-variable form so the result flows into the next. That is easier to read — and to change — than one pattern trying to do all three.

9. Exercises

Exercise 1. What are regsub's four arguments, and what does it return?

Exercise 2. What does set x [regsub -all {\s+} $line " " out] put in x?

Exercise 3. How do you get the rewritten string back directly?

Exercise 4. After a substitution that matched nothing, what does the result variable hold?

Exercise 5. What do & and \1 mean in a replacement, and how do you write a literal &?

Exercise 6. Write a one-liner collapsing all whitespace runs in $s to single spaces and trimming the ends.

Answers

1. Pattern, subject, replacement, and the name of a variable for the result. It returns the number of substitutions made, not the new string.

2. The count. The rewritten text goes into out.

3. Omit the variable name: regsub -all {\s+} $line " " returns the new string directly.

4. The unchanged input. regsub always writes a result, which is why — unlike regexp — you never need to check the return value before reading it.

5. & is the whole match and \1, \2 … are the capture groups. A literal ampersand is \&. Brace the replacement so Tcl does not eat the backslashes.

6.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set s "  a   b   c  "
puts "'[string trim [regsub -all {\s+} $s " "]]'"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
'a b c'

10. Remember

  • regsub ?switches? pattern subject replacement ?varName? — and it returns a count, not the result.
  • That is the opposite of regexp, which returns what you want and writes what you asked for.
  • Omit the variable name to get the string back directly — usually the clearest form.
  • A substitution that matched nothing still writes the result variable, with the unchanged input. No guard needed.
  • -all replaces every match; without it only the first.
  • & is the whole match, \1 and friends are the groups, and \& is a literal ampersand.
  • Brace the replacement as well as the pattern — \1 in quotes arrives as 1.
  • Several small substitutions in sequence read better than one pattern doing everything.

Where this fits

Part of the Tcl curriculum.