3. 字符集与范围:方括号 []、连字符 -、排除字符集 [^]

字符集这个概念,说白了就是「我想在这一位上匹配多个可能字符中的某一个」。你想想看,如果正则只能匹配固定的单个字符,那它的灵活性就大打折扣了。方括号 [] 就是用来解决这个问题的。

3.1 方括号 []:匹配集合中的任意一个字符

方括号定义了一个字符集合。只要目标字符串的某个位置,匹配上了集合里的任意一个字符,就算匹配成功。我刚开始学的时候,总以为它是匹配「一组字符」,其实不是,它只匹配「一个」。

核心规则: [abc] 匹配字符 a、b 或 c 中的任意一个,且只匹配一个位置。

来看几个我常用的例子:

// 匹配 "cat" 或 "bat",但不匹配 "fat"
/[cb]at/

// 匹配任意一个数字
/[0123456789]/

// 匹配一个元音字母
/[aeiou]/

我在项目中遇到过这样一个场景:需要校验用户输入的验证码,它可能是数字也可能是小写字母。我当时就用 /^[a-z0-9]{4}$/ 搞定的,简洁又高效。

3.2 连字符 -:定义字符范围

如果我要匹配所有小写字母,总不能把 26 个字母全写进方括号里吧?那也太蠢了。连字符 - 就是用来干这个的,它表示一个连续的区间。

表达式 含义 等价写法(部分)
[a-z] 匹配任意小写英文字母 [abcdefghijklmnopqrstuvwxyz]
[A-Z] 匹配任意大写英文字母 [ABCDEFGHIJKLMNOPQRSTUVWXYZ]
[0-9] 匹配任意数字 [0123456789]
[a-zA-Z] 匹配任意英文字母(大小写) 组合范围
[0-9a-fA-F] 匹配十六进制字符 数字 + 大小写 A-F

注意:连字符 - 只有在方括号内部,且不在开头或结尾时,才表示范围。如果它出现在开头(如 [-abc])或结尾(如 [abc-]),或者被转义(如 [a\-z]),它就只是一个普通字符,匹配字面意义上的连字符。

嗯,这里要注意一个坑:范围是依赖 ASCII 码顺序的。所以 [a-Z] 这种写法是错的,因为大写字母的 ASCII 码比小写字母小,不能构成一个递增的范围。我曾经见过有人写 [3-7] 想匹配数字 3 到 7,这没问题。但有人写 [z-a],这就出问题了,引擎会报错。

3.3 排除字符集 [^]:匹配不在集合中的字符

有时候我们想要的是「除了这些字符以外的任意字符」。在方括号开头加一个脱字符 ^,就表示取反。

核心规则: [^abc] 匹配除了 a、b、c 之外的任意一个字符。

举个例子:

// 匹配除了数字以外的任意字符
/[^0-9]/

// 匹配不是元音字母的任意字符
/[^aeiou]/

// 匹配不是空白字符的任意字符
/[^ \t\n\r\f\v]/

我个人习惯用排除字符集来做输入过滤。比如在表单里,我想禁止用户输入特殊符号,就可以用 /^[^!@#$%^&*()]+$/ 来校验。说白了,排除字符集就是「黑名单」思维。

小技巧: 排除字符集匹配的是「一个字符位」,而不是「一个字符串」。所以 [^abc] 能匹配 "d",也能匹配 "x",但它不能匹配空字符串。如果目标位置没有字符,匹配会失败。

3.4 字符集内部的特殊字符与转义

在方括号内部,大部分元字符都会失去特殊含义,变成普通字符。比如 .、*、+、?、(、) 等,在方括号里就只是字面符号。

但有三个字符需要特别注意:

  • ]:表示方括号的结束。如果想匹配字面意义上的 ],需要把它放在方括号的开头(如 []abc]),或者用反斜杠转义(如 [abc\])。
  • ^:放在开头表示排除。如果想匹配字面意义上的 ^,不要把它放在开头(如 [a^bc]),或者转义它。
  • -:表示范围。如果想匹配字面意义上的 -,把它放在开头或结尾(如 [-abc] 或 [abc-]),或者转义它。
// 匹配字面意义上的 ]、^ 或 -
/[]^\-]/

// 匹配点号、星号、加号
/[.*+]/

我曾经在解析日志文件时,需要匹配方括号本身。当时写了个 /\[[^\]]+\]/ 来匹配方括号内的内容。这个模式的意思是:匹配一个左方括号,然后跟着一个或多个不是右方括号的字符,最后是一个右方括号。你看,排除字符集在这里就派上大用场了。

3.5 知识体系结构图

下面这张图帮你理清字符集的核心逻辑:

字符集与范围知识结构图 字符集 [] 普通字符集 范围 [a-z] 排除 [^abc] 匹配任意一个 元字符失效 ASCII 顺序 可组合使用 黑名单思维 不能匹配空 使用要点 1. 方括号只匹配一个字符位置 2. 连字符在开头/结尾/转义时是普通字符 3. 脱字符 ^ 在开头表示排除,在其他位置是普通字符 4. 右方括号 ] 需要转义或放在开头 5. 范围必须遵循 ASCII 码递增顺序

3.6 实战中的常见陷阱

你想想看,字符集看起来简单,但实际用起来还是有不少坑的。我总结几个常见的:

  1. 范围重叠问题: 比如 [a-zA-Z0-9] 是合法的,但 [a-Z] 会报错。因为 ASCII 码中大写字母(65-90)在小写字母(97-122)之前,无法构成递增范围。
  2. 连字符位置: 如果你想匹配数字、连字符和字母,写成 [0-9a-z] 会匹配数字、小写字母和连字符吗?不会。这里的 - 在 9 和 a 之间,它表示从 ASCII 码 57(数字9)到 97(字母a)的范围,这中间包含了一些标点符号。正确的写法应该是 [0-9a-z-],把连字符放在最后。
  3. 排除字符集的误用: 我曾经用 [^abc] 想匹配「不是 abc 这三个字符的字符串」,结果它只排除了单个字符。如果要排除一个字符串,需要用负向前瞻或负向后顾,这个我们后面会讲到。

我的建议: 在写字符集时,尽量把连字符放在开头或结尾,或者显式转义。这样既清晰,又不会踩到范围定义的坑。比如匹配一个十六进制颜色值,我习惯写 #[0-9a-fA-F]{6},而不是 #[a-f0-9A-F]{6},虽然结果一样,但前者更符合阅读习惯。

好了,字符集这部分内容就这些。说白了就是三个核心操作:选一个(普通字符集)、选一段(范围)、排除一些(排除字符集)。把这些搞明白,你就能处理大部分需要「模糊匹配单个字符」的场景了。