正则表达式是定义搜索模式的一系列字符。
基础
正则表达式 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 字符类(一个字母的名字) |
特殊字符
要从字面上匹配一个特殊字符 \^$.|?*+-[]{}() ,要使用反斜杠转义。例如:\{
与左开括号匹配。
其他转义的序列:
符号 | 含义 |
---|---|
\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)?
- 如果存在 All ,则函数匹配连续的非重叠匹配。
- String 表示参数是一个字符串,否则为字节切片。
- 如果存在 Submatch ,则返回值是连续字匹配的切片。字匹配是正则表达式中带括号的子表达式的匹配。示例详见 FindSubmatch 。
- 如果存在 Index ,则通过字节索引对来识别匹配项和子匹配项。
实现
- regexp 包实现了
RE2
语法的正则表达式。 - 它支持 UTF-8 编码的字符串和 Unicode 字符类。
- 实现很高效:运行时间与输入大小呈线性相关性。
- 不支持反向引用,因为它们未能被高效实现。