Lesson 02: Matching a set of characters
Lesson objectives:
- Match any one of several characters with a character class
[…], including ranges.- Negate a class with
[^…]and use the shorthands\d,\w,\s.- State exactly what the dot
.matches — and what it does not.Prerequisites: Lesson 01 (literals,
test/match, metacharacters) | Previous << 01 | Next 03 >>
"gray" or "grey"? A literal can only pick one
Literals got you exact text, but real data is messier than that. Is it "gray" or "grey"? Is that field a digit or a letter? A literal pattern forces you to commit to one exact character in each spot, and you often can't. The character class is the metacharacter family that fixes this: it lets a single position accept any one of a set of characters. This is the first place a regex stops being a fancy string search and starts being a pattern — and it is also where the mysterious . finally gets pinned down.
Explanation
A character class is written in square brackets and matches exactly one character out of the set inside1. So [abc] matches a single "a", "b", or "c" — one character, your choice of three. The classic use: gr[ae]y matches both "gray" and "grey", because the third position accepts either "a" or "e".
Listing every character gets old, so a hyphen makes a range: [a-d] is the same as [abcd]2. The everyday ranges are [a-z] (a lowercase letter), [A-Z] (uppercase), and [0-9] (a digit). You can combine them: [A-Za-z] is any single letter of either case. Remember the whole class still matches just one character — the range only widens the menu for that one spot.
Put a caret ^ as the very first thing inside the brackets and you negate the class: [^…] matches any character that is not in the set1. So [^0-9] matches one character that is not a digit. The trap here is reading [^a-z] as "not a letter" — it actually means "not a lowercase a-z," so an uppercase "H" or a space both count as matches.
Three shorthands come up so often they have their own escapes2:
\d— a digit. Equivalent to[0-9].\w— a "word" character: any letter, digit, or underscore. Equivalent to[A-Za-z0-9_].\s— a whitespace character: space, tab, newline, and similar.
Finally, the dot. The metacharacter . matches any single character except line terminators like \n2. Read that carefully: "any character" but not a newline, unless you turn on the s (dotAll) flag. That default is a real source of bugs when a pattern is run across multi-line text.
To match a literal dot — an actual period — you escape it with a backslash: \. means "a real dot," not "any character." That is the general rule for metacharacters: put a \ in front to strip its special power and match it literally.
Worked example (follow along)
You want to detect a single hex digit — 0-9 or a-f, either case. Build the class from ranges, then test it:
Step by step: the class [0-9a-fA-F] stacks three ranges into one set, so the single position accepts any digit or any letter a-f in either case. "3", "b", and "B" each fall inside one of the ranges, so test is true; "g" is past "f" in every range, so it is false. One class, one character position, three ranges' worth of choices.
Your turn (faded example)
You want to match a single character that is not a whitespace character, using a shorthand instead of a bracket class. Fill in the blank:
Answer: the blank is \S, the uppercase counterpart of \s. As a rule, an uppercase shorthand is the negation of its lowercase form — \S is "not whitespace," just as \D is "not a digit" and \W is "not a word character." So nonSpace.test(" ") is false (a space is whitespace) and nonSpace.test("x") is true. You could also have written the negated class [^\s], which means the same thing.
Summary + what's next
A character class […] matches one character out of a set; ranges like [a-z] widen the menu; a leading caret negates it ([^…] is "anything but"); and \d / \w / \s are shorthands for digit / word / whitespace. The dot matches any character except line terminators, and a backslash escapes a metacharacter to match it literally. But every class still matches exactly one character. Next we add the other half of the story — quantifiers, which say how many times a character or class repeats, so \d can become "a whole run of digits."
Footnotes
-
regular-expressions.info: Character Classes — https://www.regular-expressions.info/charclass.html ↩ ↩2
-
MDN: Character classes — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Character_classes ↩ ↩2 ↩3
Exercises
In your console, build a class that matches a single vowel (a, e, i, o, or u, lowercase). Test it against "sky", then "sunny", then "e". Predict each result first.
You want to match a single character that is any digit OR an underscore, but nothing else. Write the class, then test it against "7", "_", and "a". Then answer: why is [^a] a bad way to express "a digit or underscore"?