Tcl Part I · Module 13 · Regular Expressions
Greedy and Non-Greedy Matching
You write a pattern to pull the first bracketed item out of a line, run it, and get back everything from the first bracket to the last one. The pattern is not wrong exactly — it is doing precisely what quantifiers do, which is take as much as they possibly can and then give back only as much as they must. Once you have seen that happen and understood the backtracking behind it, the two fixes are obvious and so is the third thing worth knowing: that greedy behaviour is sometimes exactly what you want, and switching it off by reflex causes its own bugs.
Intermediate11 min readTclPart IRegexGreedyQuantifiers
Part I · Module 13 · Chapter 13.8 · Greedy and Non-Greedy Matching
1. The surprise
set s {[a] [b]}
regexp {\[(.*)\]} $s -> captured
puts "captured : '$captured'"captured : 'a] [b'The intent was a. What came back is everything between the first [ and the last ].
2. Why: take everything, then give back the minimum
* and + are greedy. The engine does this:
\[matches the first[..*takes the entire rest of the string —a] [b].- The pattern still needs a
], and there is nothing left. - So
.*gives back one character at a time until a]can match. - The first position that works is the last
]in the string.
The result is a] [b — everything up to the final bracket.
3. Fix one: the non-greedy quantifier
Adding ? after a quantifier makes it take as little as possible, extending only when forced:
set s {[a] [b]}
regexp {\[(.*?)\]} $s -> captured
puts "captured : '$captured'"captured : 'a'Every quantifier has a non-greedy form:
| Greedy | Non-greedy | Means |
|---|---|---|
* | *? | zero or more, as few as possible |
+ | +? | one or more, as few as possible |
? | ?? | zero or one, preferring zero |
{n,m} | {n,m}? | n to m, as few as possible |
The same fix on a different shape:
puts "greedy : '[regexp {<(.+)>} {<one><two>} -> m; set m]'"
puts "non-greedy : '[regexp {<(.+?)>} {<one><two>} -> m; set m]'"greedy : 'one><two'
non-greedy : 'one'4. Fix two: a negated character class
Often better, because it says what you mean rather than how much to take:
set s {[a] [b]}
regexp {\[([^\]]*)\]} $s -> captured
puts "captured : '$captured'"captured : 'a'[^\]]* means "any number of characters that are not a closing bracket". It cannot overshoot, because the character that would end the match is excluded from what it can consume.
5. The third fix people reach for, and why not
The instinct when a pattern captures too much is to add more pattern:
set s {[a] [b]}
regexp {\[(.*)\] \[} $s -> captured
puts "captured : '$captured'"captured : 'a'That works — and only because there happen to be exactly two bracketed items with a space between them. Give it one item and it fails entirely:
puts "on one item : [regexp {\[(.*)\] \[} {[a]} -> m]"on one item : 0Whereas both real fixes still work:
puts "non-greedy : [regexp {\[(.*?)\]} {[a]} -> m] -> '$m'"
puts "negated : [regexp {\[([^\]]*)\]} {[a]} -> m] -> '$m'"non-greedy : 1 -> 'a'
negated : 1 -> 'a'6. Greedy is sometimes exactly right
Switching greediness off by reflex causes its own bugs. "Everything before the last slash" is a greedy question:
set path "a/b/c.txt"
puts "greedy : '[regexp {^(.*)/} $path -> m; set m]'"
puts "non-greedy : '[regexp {^(.*?)/} $path -> m; set m]'"greedy : 'a/b'
non-greedy : 'a'For a directory name you want the greedy one; for the first path component you want the non-greedy one. Neither is the bug — they answer different questions.
| You want | Quantifier |
|---|---|
| Up to the last occurrence | greedy .* |
| Up to the first occurrence | non-greedy .*? or a negated class |
7. Greediness affects where the match ends, not whether it matches
A useful thing to know when debugging: changing greediness never turns a match into a non-match of the whole pattern:
set s "aaa"
puts "greedy : [regexp {^a+$} $s]"
puts "non-greedy: [regexp {^a+?$} $s]"greedy : 1
non-greedy: 1Both match, because the anchors force the whole string either way. Greediness only decides how much a quantifier takes when there is a choice — so if a pattern is not matching at all, greediness is not the cause and you should look at the classes and anchors instead.
8. A worked example
Pulling fields out of a bracketed log line, three ways:
set line {[2026-01-05] [ERROR] cannot open [input.csv] for reading}
puts "greedy, first group:"
regexp {\[(.*)\]} $line -> all
puts " '$all'"
puts "non-greedy, all three:"
foreach {whole field} [regexp -all -inline {\[(.*?)\]} $line] {
puts " '$field'"
}
puts "negated class, all three:"
foreach {whole field} [regexp -all -inline {\[([^\]]*)\]} $line] {
puts " '$field'"
}greedy, first group:
'2026-01-05] [ERROR] cannot open [input.csv'
non-greedy, all three:
'2026-01-05'
'ERROR'
'input.csv'
negated class, all three:
'2026-01-05'
'ERROR'
'input.csv'The greedy version captured from the first bracket to the last, swallowing two fields and the text between them. The other two found all three fields correctly, and the negated class is the one to keep.
9. Exercises
Exercise 1. Why does {\[(.*)\]} on [a] [b] capture a] [b?
Exercise 2. Give two ways to make it capture a, and say which you prefer.
Exercise 3. Write the non-greedy forms of *, + and {2,5}.
Exercise 4. Why is adding \[ to the end of the pattern a poor fix?
Exercise 5. Give a case where greedy is the correct choice.
Exercise 6. A pattern matches nothing at all. Is greediness a likely cause?
Answers
1. Because * is greedy: it first takes the whole rest of the string, then gives back characters one at a time until the following \] can match. The first position that works is the last ], so the capture runs to there.
2. {\[(.*?)\]} with a non-greedy quantifier, or {\[([^\]]*)\]} with a negated character class. The negated class is preferable when there is an obvious terminator — it describes the content rather than the search strategy and cannot overshoot.
3. *?, +? and {2,5}?.
4. Because it bakes the surrounding context — "another bracket follows" — into the pattern, so it only works on input shaped like the example. A line with a single bracketed item fails to match at all.
5. Anything asking for the last occurrence: everything before the final slash in a path, the last dot in a filename, the outermost pair of brackets. Non-greedy would stop at the first.
6. No. Greediness decides how much a quantifier consumes when there is a choice, not whether the pattern can match. A total non-match points at the character classes, the anchors, or an escape that was eaten by quoting.
10. Remember
- Quantifiers are greedy: they take as much as possible, then backtrack only as far as the rest of the pattern requires.
- So
.*between two delimiters runs to the last one, not the first. ?after a quantifier makes it non-greedy —*?,+?,{n,m}?.- A negated character class is usually better —
[^\]]*cannot overshoot and describes the content. - Do not fix greediness by adding surrounding context — that encodes your test data into the pattern.
- Greedy is correct when you want the last occurrence, non-greedy when you want the first.
- Greediness changes where a match ends, not whether it matches — a total non-match has a different cause.
- For paths, use the
filecommands rather than either.
Where this fits
Part of the Tcl curriculum.
