Tcl Part I · Module 8 · Conditions and Control Flow
switch Matching Modes: -exact, -glob, -regexp
By default switch compares its value to each pattern character for character, and that is the right behaviour most of the time. Two options change it: one turns the patterns into the glob patterns you already learned for string match, and one turns them into regular expressions. This chapter covers all three, plus the separator that appears in every switch in this module without having been explained. That separator is usually taught as a superstition — always write it, nobody says why. There is a real answer, it is more specific than the folklore, and it is worth two minutes.
Foundation11 min readTclPart IControl flowswitchPatterns
Part I · Module 8 · Chapter 8.5 · switch Matching Modes: -exact, -glob, -regexp
1. -exact is the default
Without an option, patterns are compared character for character:
switch -- "fa" {
fa* { puts "matched the glob" }
fa { puts "matched exactly" }
}matched exactlyThe pattern fa* did not match the value fa, because in exact mode * is just an asterisk. Only the literal fa matched.
Writing -exact explicitly is legal and says what you mean:
switch -exact -- "fa" {
fa* { puts "glob" }
fa { puts "exact" }
}exact2. -glob
-glob turns the patterns into the glob language from Chapter 5.5 — * for any run of characters, ? for exactly one, [...] for a character set:
foreach file {report.log data.csv notes.txt archive.tar.gz} {
set kind [switch -glob -- $file {
*.log { expr {"log file"} }
*.csv { expr {"spreadsheet"} }
*.tar.gz { expr {"archive"} }
default { expr {"something else"} }
}]
puts [format "%-16s %s" $file $kind]
}report.log log file
data.csv spreadsheet
notes.txt something else
archive.tar.gz archiveOrder matters as always — the first matching pattern wins — so put the more specific patterns first when two could both match.
3. -regexp
For anything glob cannot express:
foreach s {width=32 depth=16 mode=fast bogus} {
set out [switch -regexp -- $s {
{^\w+=\d+$} { expr {"numeric setting"} }
{^\w+=\w+$} { expr {"text setting"} }
default { expr {"not a setting"} }
}]
puts [format "%-12s %s" $s $out]
}width=32 numeric setting
depth=16 numeric setting
mode=fast text setting
bogus not a settingBraces around each regular expression matter: they stop Tcl substituting $ and \ before the pattern reaches switch, exactly as braces do everywhere else.
-matchvar captures the parenthesised groups into a list, with the whole match first:
switch -regexp -matchvar m -- "width=32" {
{^(\w+)=(\d+)$} {
puts "key '[lindex $m 1]' has value '[lindex $m 2]'"
}
default { puts "no match" }
}key 'width' has value '32'4. -nocase
Works on its own and alongside the other modes:
switch -- "FAST" {
fast { puts "hit" }
default { puts "miss" }
}
switch -nocase -- "FAST" {
fast { puts "hit" }
default { puts "miss" }
}
switch -nocase -glob -- "REPORT.LOG" {
*.log { puts "a log file" }
default { puts "miss" }
}miss
hit
a log file5. What -- actually does
-- marks the end of the options. Everything after it is data, even if it starts with a dash.
The usual explanation stops there, which leaves you wondering why the switches in Chapter 8.4 all had one when none of their values began with a dash. Here is the specific answer.
Tcl stops looking for options once only two arguments remain — the value and the body list. So in the common braced form, a value starting with a dash is already safe:
set arg "-zzz"
switch $arg {
-zzz { puts "matched" }
default { puts "default" }
}matchedNo error, even though -zzz is not a real option. Now write the same thing in the multi-argument form, where more than two arguments remain:
set arg "-v"
switch $arg -v {puts "matched"} default {puts "default"}bad option "-v": must be -exact, -glob, -indexvar, -matchvar, -nocase, -regexp, or --There it is. Tcl had more than two arguments left, so it tried to read -v as an option. With -- it works:
set arg "-v"
switch -- $arg -v {puts "matched"} default {puts "default"}matched6. Choosing a mode
| You want to | Use |
|---|---|
| Compare against fixed strings | nothing (or -exact) |
| Match filenames, suffixes, prefixes | -glob |
| Match structure — digits, anchors, groups | -regexp |
| Ignore case | add -nocase |
| Capture part of the match | -regexp -matchvar |
Default to exact. It is the fastest, it has no surprises, and a pattern containing * or . in exact mode means what it looks like rather than something cleverer.
7. A worked example
Classifying input lines with the mode chosen per job:
proc classify {line} {
# exact first: the few fixed keywords
switch -- $line {
BEGIN { return "marker: begin" }
END { return "marker: end" }
}
# then structure
switch -regexp -- $line {
{^#} { return "comment" }
{^\s*$} { return "blank" }
{^(\w+)\s*=} { return "assignment" }
}
# then shape
switch -glob -- $line {
*.log { return "log filename" }
* { return "unrecognised" }
}
}
foreach l {BEGIN "# a note" "width = 32" "" "run.log" "???"} {
puts [format "%-12s %s" "'$l'" [classify $l]]
}'BEGIN' marker: begin
'# a note' comment
'width = 32' assignment
'' blank
'run.log' log filename
'???' unrecognisedThree switch commands, each in the mode that suits its question, each falling through to the next when nothing matched. Note the first two have no default — that is how they hand control onward.
8. Exercises
Exercise 1. Why does the pattern fa* fail to match the value fa in switch -- "fa" { fa* {...} }?
Exercise 2. Write a switch classifying a.txt, b.log and c into text, log and unknown.
Exercise 3. Explain in one sentence what -- does, and give the exact error you get without it in the multi-argument form.
Exercise 4. Which mode for each: (a) HTTP status codes 200/404/500; (b) filenames ending .v or .sv; (c) strings like clk_div_8 where you want the number; (d) YES/yes/Yes.
Exercise 5. Use -regexp -matchvar to pull the key and value out of timeout=300.
Exercise 6. Someone writes switch -regexp -- $f { *.log {...} } meaning to match log filenames. Why is that not the test they think it is?
Answers
1. Because the default mode is -exact, where * is simply an asterisk character. The pattern is the three literal characters f, a, *, and the value fa does not contain an asterisk.
2.
foreach f {a.txt b.log c} {
puts [switch -glob -- $f {
*.txt { expr {"text"} }
*.log { expr {"log"} }
default { expr {"unknown"} }
}]
}text
log
unknown3. It ends option processing, so the next argument is the value even if it begins with a dash. Without it, the multi-argument form gives bad option "-v": must be -exact, -glob, -indexvar, -matchvar, -nocase, -regexp, or --.
4. (a) exact — they are fixed strings; (b) -glob with *.v and *.sv; (c) -regexp -matchvar to capture the digits; (d) -nocase.
5.
switch -regexp -matchvar m -- "timeout=300" {
{^(\w+)=(\d+)$} { puts "[lindex $m 1] -> [lindex $m 2]" }
default { puts "no match" }
}timeout -> 3006. In a regular expression * means "zero or more of the previous item" and . means "any character", so *.log is not "anything ending in .log" — it is a malformed or at best accidental pattern. The glob intent needs -glob, or the regexp {\.log$}.
9. Remember
-exactis the default.*and.in a pattern are ordinary characters unless you ask for another mode.-globgives thestring matchlanguage:*,?,[...].-regexpgives regular expressions; brace each pattern so$and\survive.-matchvarcaptures the whole match and its groups into a list.-nocaseworks alone or combined with-globand-regexp.--ends option processing. Tcl happens to stop looking for options with two arguments left, so the braced form is lenient — do not rely on it.- Without
--, the multi-argument form givesbad option "-v": must be -exact, -glob, ..., which is also a free list of every valid option. - First match wins in every mode, so order specific patterns before general ones.
- Choose the mode deliberately — a
.means different things to glob and to regexp.
Where this fits
Part of the Tcl curriculum.
