Tcl Part I · Module 5 · Strings
Indexing and Ranges: string index and string range
Two subcommands get at the characters inside a string: one for a single character, one for a span. Both are straightforward, and both come with a feature that makes Tcl noticeably pleasanter than languages where you have to subtract from a length — you can write end and end-3 directly as positions. They also share a behaviour worth knowing before you rely on it: asking for a position that does not exist gives you an empty string rather than an error, which is convenient until it quietly hides a mistake.
Foundation12 min readTclPart IStringsIndexingSlicing
Part I · Module 5 · Chapter 5.3 · Indexing and Ranges
1. Positions Start at Zero
set line "report_2024.txt"
puts "index 0 -> [string index $line 0]"
puts "index 6 -> [string index $line 6]"
puts "index 11 -> [string index $line 11]"index 0 -> r
index 6 -> _
index 11 -> .The first character is at 0, as in most languages.
Counting positions by hand is error-prone, which is what the next section is for.
2. Counting From the Right
end is the last position, and end-N counts back from it:
set line "report_2024.txt"
puts "index end -> [string index $line end]"
puts "index end-3 -> [string index $line end-3]"index end -> t
index end-3 -> .These are index keywords, not arithmetic you perform. You never have to write this:
string index $line [expr {[string length $line] - 1}]That is noisier and is where off-by-one errors come from. end is exact and says what you mean.
3. Spans With string range
set line "report_2024.txt"
puts "range 0 5 -> [string range $line 0 5]"
puts "range 7 end -> [string range $line 7 end]"
puts "range end-2 end -> [string range $line end-2 end]"range 0 5 -> report
range 7 end -> 2024.txt
range end-2 end -> txtBoth ends are inclusive. 0 5 gives six characters, positions 0 through 5.
That is worth stating because many languages use an exclusive upper bound. In Tcl, string range $s 0 5 and a Python slice s[0:5] give different results — Tcl's includes position 5.
4. Arithmetic Inside a Position
Positions accept simple arithmetic, which is what makes these commands useful after a search:
set s "key:value"
set i 3
puts "before the colon -> [string range $s 0 $i-1]"
puts "after the colon -> [string range $s $i+1 end]"before the colon -> key
after the colon -> value$i-1 and $i+1 are written directly in the position, with no expr and no brackets.
This is the shape you will use constantly once you can find a position, which is the next chapter. Splitting a line at its first colon becomes two readable lines rather than four noisy ones.
5. Out of Range Is Empty, Not an Error
set s "abc"
puts "index 99 -> <[string index $s 99]>"
puts "index -1 -> <[string index $s -1]>"
puts "range 5 10 -> <[string range $s 5 10]>"
puts "range 2 0 -> <[string range $s 2 0]>"
puts "range -5 1 -> <[string range $s -5 1]>"index 99 -> <>
index -1 -> <>
range 5 10 -> <>
range 2 0 -> <>
range -5 1 -> <ab>None of these is an error. Every out-of-range request gives an empty string, and a range whose end is before its start gives an empty string too.
The last line is the interesting one: a range starting before the string is clamped to the beginning, so -5 1 gives the first two characters.
6. A Worked Example
Taking a fixed-width record apart:
set record "RX 00421 OK "
set code [string trim [string range $record 0 3]]
set number [string trim [string range $record 4 10]]
set status [string trim [string range $record 11 end]]
puts "code=<$code> number=<$number> status=<$status>"code=<RX> number=<00421> status=<OK>Each field is a known span, pulled out by position and trimmed — Chapter 5.8.
Fixed-width records are common in tool output and legacy formats. When the fields are separated by a character instead, Chapter 5.13 is the better approach.
7. Building a String by Position
string range reads; it does not write. To change part of a string you use string replace, which is Chapter 5.9:
set s "hello"
puts "pieces: [string range $s 0 0][string toupper [string range $s 1 end]]"
puts "original unchanged: $s"pieces: hELLO
original unchanged: helloNothing modified s — Chapter 5.1 §7.
8. Common Mistakes
Expecting an exclusive end. Both ends are inclusive. §3.
Computing the last position with string length. Use end. §2.
Assuming out-of-range raises an error. It gives "". §5.
Using a position from a search without checking it. Chapter 5.4.
Expecting string range to modify the string. It returns a new one. §7.
Using positions where a separator would be simpler. If the data has delimiters, use Chapter 5.13.
9. Exercises
Exercise 1 — read positions. For set s "measurement", predict then check: string index $s 0, string index $s 4, string index $s end, string index $s end-1.
Exercise 2 — spans. From the same string, extract measure, ment, and the middle sure, using string range three times.
Exercise 3 — inclusive ends. How many characters does string range $s 2 6 return? Work it out before running, then confirm.
Exercise 4 — out of range. Predict the output of all five lines in §5 before running them. Which one did you expect to error?
Exercise 5 — split at a position. With set s "name=value" and set i 4, produce name and value using index arithmetic and no expr.
Exercise 6 — a fixed-width line. Given "ERR 0042 timeout ", extract the three fields by position and trim each. Then say what would break if a field were one character wider.
10. Remember
- Positions start at 0.
string indexgives one character,string rangegives a span. - Both ends of a range are inclusive.
0 5is six characters. endandend-Nare index keywords, so you never subtract from a length.- Simple arithmetic works inside a position:
$i-1,$i+1. - Out of range gives an empty string, never an error — including a reversed range.
- A range starting before the string is clamped to the beginning.
- That convenience hides mistakes, so check positions that came from a search or a calculation.
- Neither command modifies the string.
string replaceis for changing text.
Next: finding where something is, so you have a position worth using.
Where this fits
Part of the Tcl curriculum.
