Tcl Part I · Module 5 · Strings
Replacing and Mapping: string replace and string map
Two commands change text, and they answer different questions. One takes positions and swaps out a span, which makes it the writing counterpart to the range reading you saw earlier. The other takes pairs of old and new text and applies all of them in a single pass, which makes it both fast and unusually predictable — predictable enough that it can swap two values in one call, something a sequence of individual replacements cannot do. For literal substitutions it is also the tool to reach for long before a regular expression.
Foundation12 min readTclPart IStringsReplacementMapping
Part I · Module 5 · Chapter 5.9 · Replacing and Mapping
1. Two Different Questions
string replace— "change the characters at these positions".string map— "change this text wherever it appears".
Positions versus content. Picking the wrong one is the main way to make this harder than it is.
2. string replace Works on Positions
set line "0123456789"
puts "replace 0 3 with XX -> <[string replace $line 0 3 "XX"]>"
puts "replace 0 3 (delete) -> <[string replace $line 0 3]>"
puts "replace end-1 end -> <[string replace $line end-1 end "!"]>"replace 0 3 with XX -> <XX456789>
replace 0 3 (delete) -> <456789>
replace end-1 end -> <01234567!>Three things to notice.
The positions follow Chapter 5.3 exactly — inclusive on both ends, and end and end-N work.
Omit the replacement and the span is deleted. That is the normal way to cut characters out.
The replacement need not be the same length. Four characters became two.
Like everything else in Chapter 5.1 §7, it returns a new string and leaves the original alone.
3. string map Works on Content
set label "my report/draft"
puts "<[string map {" " "_" "/" "-"} $label]>"<my_report-draft>The argument is a list of pairs: find this, replace with this, repeated. Here spaces become underscores and the slash becomes a dash, in one call.
That alone is useful — one command instead of a chain. But the property that matters is how it does it.
4. One Pass, No Cascading
string map scans the text once, left to right. A replacement it has already made is never looked at again:
puts [string map {a b b c} "aaa"]Predict that. The pairs say "a becomes b" and "b becomes c". If the replacements cascaded, every a would become b and then c.
bbbbbb, not ccc. Each a was replaced by b, and the scan moved past it.
That is what makes a genuine swap possible:
puts [string map {cat dog dog cat} "cat dog"]dog catThe two values exchanged places in one call.
Try that with two sequential replacements and it cannot work — the first turns every cat into dog, and the second then turns all the dogs, including the new ones, back into cats. You would need a temporary placeholder. string map needs nothing.
5. Order Within the Pairs Does Matter
One thing you do have to think about. At each position, the first matching pair in the list wins:
puts [string map {ab X abc Y} "abc"]
puts [string map {abc Y ab X} "abc"]Xc
YSame input, same pairs, different order, different answer.
In the first, ab was listed first and matched at position 0, leaving c behind. In the second, abc was listed first and matched the whole thing.
Put longer patterns before shorter ones when one is a prefix of another.
6. Ignoring Case
puts [string map -nocase {ERROR ok} "error and Error"]ok and okThe pattern matches regardless of case; the replacement is used exactly as written.
7. Where You Will Use These
Making a safe filename:
set title "Q3 Report: Draft/Final"
puts [string map {" " "_" ":" "" "/" "-"} $title]Q3_Report_Draft-FinalNote the empty replacement — that is how you delete a character.
Escaping for output:
set text {a "quoted" value}
puts [string map {"\"" "\\\""} $text]a \"quoted\" valueCutting a prefix by position:
set line "INFO: starting run"
puts [string replace $line 0 5 ""]starting runThough when the prefix length is not fixed, finding it first — Chapter 5.4 — is safer than counting characters.
8. Which to Reach For
| You want | Use |
|---|---|
| change a span you know the position of | string replace |
| delete a span | string replace with no replacement |
| change all occurrences of some text | string map |
| apply several substitutions at once | string map |
| swap two values | string map |
| remove characters from the ends only | string trim |
| change something matching a pattern | a regular expression, Module 13 |
The last row is the boundary. string map handles literal text. The moment you need "any digit" or "one or more spaces", you need Module 13 — and that module opens by telling you to come back here when the text is literal.
9. Common Mistakes
Expecting replacements to cascade. §4. They do not, and that is the feature.
Listing a short pattern before a longer one that starts with it. §5.
Using string replace with positions that came from a search without checking them. Chapter 5.4 §4.
Reaching for a regular expression for literal substitutions. string map is faster, shorter and has nothing to escape.
Forgetting the pairs are a list. An odd number of elements is an error.
Expecting it to modify in place. It returns a new string.
10. Exercises
Exercise 1 — positions. From "abcdefgh", produce abXXfgh, abfgh, and abcdefg! using string replace three times.
Exercise 2 — predict the pass. Before running, say what each gives:
string map {x y y z} "xy"
string map {1 2 2 3 3 4} "123"
string map {aa b} "aaaa"Exercise 3 — the swap. Exchange on and off in the text "led on, fan off, pump on" with one call. Then try doing it with two sequential string map calls and explain what goes wrong.
Exercise 4 — order. Write a map that turns "dog" into "D" and "do" into "X". Get it wrong first by listing do before dog, then fix it.
Exercise 5 — a safe filename. Turn "Run 7: results (final)/v2" into something with no spaces, colons, brackets or slashes, using one string map.
Exercise 6 — pick the command. For each, say replace or map: removing the first four characters; turning every tab into four spaces; blanking out characters 10 to 15; replacing every , with ;.
11. Remember
string replaceworks on positions, using the same index rules asstring range, with both ends inclusive.- Omit the replacement to delete a span. The replacement need not be the same length.
string mapworks on content, taking a list of find-and-replace pairs.- It makes one left-to-right pass and never revisits what it has already replaced.
- That is what lets it swap two values in a single call.
- Within the pairs, the first match at a position wins — list longer patterns first.
-nocasematches regardless of case.- An empty replacement deletes.
- Use it for literal text; use a regular expression only when you need a pattern.
Next: building a string up a piece at a time.
Where this fits
Part of the Tcl curriculum.
