Regular Expressions: Stop Copy-Pasting Patterns You Can't Read · 第 2 / 6 节

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

  1. regular-expressions.info: Character Classes — https://www.regular-expressions.info/charclass.html 2

  2. MDN: Character classes — https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions/Character_classes 2 3

Exercises

Exercise 1 · Level 1 (warm-up)

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.

Done criteria · checked locally
Exercise 2 · Level 2 (advanced)

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"?

Done criteria · checked locally