Go 正则表达式入门

2020/06/22

正则表达式是定义搜索模式的一系列字符。

基础

正则表达式 a.b 匹配了任何开头一个 a 结尾一个 b 的字符串,中间一个任意字符(点号匹配任意字符)。

使用 regexp.MatchString 函数来验证:

matched, err := regexp.MatchString("a.b", "aaxbb")
fmt.Println(match, err) // true nil

如果要检查整个字符串匹配 a.b ,就需要锚定正则表达式的开始和结束:

matched, err := regexp.MatchString(`^a.b$`, "aaxbb")
fmt.Println(matched) // false

类似的,我们可以只用开始或结束锚点检查字符串以什么开头以什么结尾

编译

对于更复杂的查询,我们需要将正则表达式编译成一个 Regexp 对象。有两个可选项:

re1, err := regexp.Compile(`regexp`) // 正则表达式不合法时报错(error)
re2 := regexp.MustCompile(`regexp`) // 正则表达式不合法时恐慌(panic)

原始字符串

编写正则表达式时使用 `原始字符串` 很方便,因为普通字符串文字和正则表达式都对特殊字符使用反斜杠。

由反引号界定的原始字符串将按照字面意义进行解释,反斜线在其中没有特殊含义。

速查表

选择和分组

正则 含义
xy x 后跟 y
x|y x 或 y, 优先 x
xy|z 等于 (xy)|z
xy* 等于 x(y*)

重复(贪婪和非贪婪)

正则 含义
x* 0 个或多个 x ,优先匹配多个
x*? 最少匹配(非贪婪)
x+ 1 个或多个 x ,优先匹配多个
x+? 最少匹配(非贪婪)
x? 0 个或 1 个 x ,优先 1 个
x?? 优先 0 个
x{n} 确切的 n 个 x

字符类型

正则 含义
. 任意字符
[ab] 字符 a 或字符 b
[^ab] 除了 a 或 b 之外的任意字符
[a-z] 从 a 到 z 的任意字符
[a-z0-9] 任何从 a 到 z 或者 0 到 9 的字符
\d 一个数字: [0-9]
\D 一个非数字: [^0-9]
\s 一个空白字符: [\t\n\f\r]
\S 一个非空白字符: [^\t\n\f\r]
\w 一个文字符号: [0-9A-Za-z_]
\W 一个非文字符号: [^0-9A-Za-z]
\p{Greek} Unicode 字符类*
\pN Unicode 字符类(一个字母的名字)
\P{Greek} 非 Unicode 字符类*
\PN 非 Unicode 字符类(一个字母的名字)

RE2: Unicode 字符类

特殊字符

要从字面上匹配一个特殊字符 \^$.|?*+-[]{}() ,要使用反斜杠转义。例如:\{ 与左开括号匹配。

其他转义的序列:

符号 含义
\t 横向制表符 = \011
\n 换行符 = \012
\f 换页 = \014
\r 回车 = \015
\v 纵向制表符 = \013
\123 八进制字符代码(最多三个数字)
\x7F 十六进制字符代码(必须是两个数字)

文本边界锚点

符号 含义
\A 在文本开头
^ 在文本或者行的开头
$ 文本结尾
\z 文本结尾
\b 在 ASCII 字边界
\B 非 ASCII 字边界

大小写不敏感与多行匹配

要改变默认的匹配规则,你可以将一组标志添加到正则表达式的开头。

例如,前缀 (?is) 使得匹配大小写不敏感并且允许点号 . 匹配 \n 。(默认情况下是大小写敏感的并且点号不匹配换行符 \n 。)

标志 含义
i 大小写不敏感
m ^$ 不仅匹配开始/结束文本还匹配开始/结束行(多行模式)
s . 匹配 \n (单行模式)

代码例子

匹配第一个

使用 FindString 方法去查找第一个匹配上的文本,如果没有匹配项,返回值是一个空字符串。

re := regexp.MustCompile(`foo.?`)
fmt.Printf("%q\n", re.FindString("seafood fool")) // "food"
fmt.Printf("%q\n", re.FindString("meat"))         // ""

定位

使用 FindStringIndex 方法找到字符串 s 中的 loc,即第一个匹配到的下标位置。匹配结果就是 s[loc[0]:loc[1]]。返回值为空(nil)表示未匹配到。

re := regexp.MustCompile(`ab?`)
fmt.Println(re.FindStringIndex("tablett"))    // [1 3]
fmt.Println(re.FindStringIndex("foo") == nil) // true

所有匹配

使用 FindAllString 方法所有匹配的文本。返回值为空表示未匹配。

该方法接收一个整数 n 作为参数,如果 n >= 0 ,函数就最多匹配 n 次。

re := regexp.MustCompile(`a.`)
fmt.Printf("%q\n", re.FindAllString("paranormal", -1)) // ["ar" "an" "al"]
fmt.Printf("%q\n", re.FindAllString("paranormal", 2))  // ["ar" "an"]
fmt.Printf("%q\n", re.FindAllString("graal", -1))      // ["aa"]
fmt.Printf("%q\n", re.FindAllString("none", -1))       // [] (nil slice)

替换

使用 ReplaceAllString 方法可以替换所有匹配结果。它返回一个字符串副本,替换了所有的正则匹配到的字符。

re := regexp.MustCompile(`ab*`)
fmt.Printf("%q\n", re.ReplaceAllString("-a-abb-", "T")) // "-T-T-"

分割

使用 Split 方法通过正则表达式将字符串切分成由多个子串。它返回这些表达式匹配之间的子字符串的一个切片。返回值为空表示无匹配。

该方法接收一个整数 n ;当 n >= 0 时,函数最多匹配上 n 次。

a := regexp.MustCompile(`a`)
fmt.Printf("%q\n", a.Split("banana", -1)) // ["b" "n" "n" ""]
fmt.Printf("%q\n", a.Split("banana", 0))  // [] (nil slice)
fmt.Printf("%q\n", a.Split("banana", 1))  // ["banana"]
fmt.Printf("%q\n", a.Split("banana", 2))  // ["b" "nana"]

zp := regexp.MustCompile(`z+`)
fmt.Printf("%q\n", zp.Split("pizza", -1)) // ["pi" "a"]
fmt.Printf("%q\n", zp.Split("pizza", 0))  // [] (nil slice)
fmt.Printf("%q\n", zp.Split("pizza", 1))  // ["pizza"]
fmt.Printf("%q\n", zp.Split("pizza", 2))  // ["pi" "a"]

更多函数

总共有 16 种函数按照以下命名模式:

Find(All)?(String)?(Submatch)?(Index)?

实现

参考