Tcl Part I · Module 5 · Strings
scan: Reading Fields Out of a Fixed Line
This is the opposite of the previous chapter. Where format takes values and produces a line, scan takes a line and produces values — using the same conversion vocabulary, so most of it you already know. The part that needs attention is what it returns, because it does not raise an error when a line does not match the pattern. It tells you how many fields it managed to read, and a line that matched only halfway leaves the rest of your variables unset while the script carries on perfectly happily.
Foundation11 min readTclPart IStringsscanParsing
Part I · Module 5 · Chapter 5.12 · scan: Reading Fields Out of a Fixed Line
1. The Opposite of format
set line "widget 42 3.5"
scan $line "%s %d %f" name count value
puts "name=$name count=$count value=$value"name=widget count=42 value=3.5The conversions are the same ones from Chapter 5.11 — %s for text, %d for a whole number, %f for a real one. The difference is direction: here they say what to read, and the trailing arguments are variable names to store into.
No $ on those names, for the same reason as set and append.
2. What It Returns
This is the chapter:
set line "widget 42 3.5"
set n [scan $line "%s %d %f" name count value]
puts "conversions that succeeded: $n"conversions that succeeded: 3scan returns how many conversions succeeded, not whether the line was valid. Three conversions, three successes.
3. It Does Not Error on a Bad Line
set n [scan "abc" "%d" number]
puts "returned: $n"returned: 0Zero conversions, no error. The variable number was never set.
That is survivable because zero is obviously wrong. The dangerous case is in between.
4. The Partial Match
set line "oops"
set n [scan $line "%s %d %f" name count value]
puts "returned: $n"
puts "name is: $name"
puts "count is: [info exists count]"returned: 1
name is: oops
count is: 0One conversion succeeded. name was set; count and value were not set at all.
If the script had gone straight on to use $count, it would have hit can't read "count": no such variable — which is at least an error, several lines away from the cause. And if count happened to hold a value from a previous iteration of a loop, there would be no error at all and the old value would be used as though it were new data.
5. Literal Text in the Pattern
Anything that is not a conversion must appear in the input exactly:
set date "2024-10-07"
if {[scan $date "%d-%d-%d" year month day] == 3} {
puts "year=$year month=$month day=$day"
}year=2024 month=10 day=7The dashes in the pattern matched the dashes in the input. This is how you parse a structured field without splitting it.
Note day came back as 7, not 07 — %d produces a number, and the leading zero is gone. That is usually what you want, and it neatly sidesteps the octal problem from Chapter 3.5 §6: scan is the recommended way to read a zero-padded field precisely because it gives you a decimal number.
6. Whitespace and %s
A space in the pattern matches any amount of whitespace:
puts [scan "a 42" "%s %d" w n]
puts "w=$w n=$n"2
w=a n=42Four spaces matched the single space in the pattern.
Be aware that %s reads up to the next whitespace, so it cannot capture a value containing spaces:
scan "two words here" "%s" first
puts "captured: <$first>"captured: <two>For a field that may contain spaces, use character classes — %[^,] reads everything up to a comma — or use Chapter 5.13 instead.
set n [scan "key=some value" "%\[^=\]=%s" k v]
puts "n=$n k=<$k>"n=2 k=<key>7. When Not to Use It
scan suits lines with a predictable shape. It is the wrong tool when:
- the fields are separated by a consistent character and you just want them all — use
split, Chapter 5.13 - the shape varies from line to line — use a regular expression, Module 13
- you only need one value from a known position — use
string range, Chapter 5.3
The sweet spot is a fixed line with typed fields, where you want the numbers to come back as numbers without converting them yourself.
8. A Complete Example
set lines {
"widget 42 3.5"
"gadget 7 1.25"
"broken line"
"doodad 19 0.5"
}
set total 0.0
set used 0
set skipped 0
foreach line $lines {
set line [string trim $line]
if {$line eq ""} { continue }
if {[scan $line "%s %d %f" name count value] != 3} {
incr skipped
continue
}
set total [expr {$total + $count * $value}]
incr used
}
puts [format "%d lines used, %d skipped, total %.2f" $used $skipped $total]3 lines used, 1 skipped, total 165.25The count check is what makes the bad line survivable, and the script reports how many it skipped rather than quietly dropping it — Chapter 3.11 §4.
9. Common Mistakes
Ignoring the return value. §4. The main one.
Checking > 0 instead of the exact count. A partial match passes that test.
Putting $ on the variable names. They are names, like set and append.
Expecting %s to capture spaces. It stops at whitespace. §6.
Using it on irregular text. §7.
Assuming unset variables are empty. They are unset, which is an error to read — Chapter 3.2 §8.
10. Exercises
Exercise 1 — three fields. Parse "alpha 10 2.5" into three variables and print the count that scan returned.
Exercise 2 — the partial match. Run scan "alpha" "%s %d %f" a b c and print the return value and info exists c. Explain what a script that ignored the count would do next.
Exercise 3 — a date. Parse "07/10/2024" into day, month and year. Confirm the leading zero is gone and say why that matters.
Exercise 4 — the guard. Take Exercise 1 and add the exact-count check. Then feed it "alpha ten 2.5" and confirm it is rejected.
Exercise 5 — a field with spaces. Parse "name=My Report" into a key and a value, keeping the space in the value. Which technique did you need?
Exercise 6 — pick the tool. For each, say scan, split or string range: a comma-separated row; a fixed-width record; a line of word number number; a log line whose format changes between tools.
11. Remember
scanisformatin reverse, with the same conversions.- The trailing arguments are variable names, with no
$. - It returns how many conversions succeeded — not whether the line was valid.
- A bad line does not raise an error. It returns a smaller number.
- A partial match leaves later variables unset, and the script carries on.
- Check the exact count:
if {[scan ...] != 3} { ... }. - Literal text in the pattern must appear in the input, which is how you parse structured fields.
%dreturns a decimal number, so it is the safe way to read a zero-padded field.%sstops at whitespace. Use a character class orsplitfor fields containing spaces.- Use it for predictable lines only.
One chapter remains, and it is the one that connects this whole module to the next.
Where this fits
Part of the Tcl curriculum.
