Regexp
正则表达式语法概述(熟悉可跳过)
对于go 正则表达式语法也可以运行下列命令查看go doc regexp/syntax
单字符集
符号 | 描述 |
---|---|
. | 匹配任意字符,可能包含'\n'(当 flag s = true) |
[xyz] | 字符集(匹配字符集中任意字符) |
[^xyz] | 否定字符集(排除字符集中任意字符) |
\d | Perl字符集(匹配任意数字) |
\D | 否定Perl字符集(匹配任意非数字) |
[[:alpha:]] | ASCII字符集(匹配ASCII字符集) |
[[:^alpha:]] | 否定ASCII字符集(排除ASCII字符集) |
\pN | Unicode字符集(字符名) |
\p{Greek} | Unicode字符集 |
\PN | 否定Unicode字符集(一个字符名) |
\P{Greek} | 否定Unicode字符集 |
复合
符号 | 描述 |
---|---|
xy | 匹配字符x后接字符y |
x|y | 匹配字符x或字符y |
重复
符号 | 描述 |
---|---|
x* | 匹配零次或更多x,倾向于更多 |
x+ | 匹配一次或更多x,倾向于更多 |
x? | 匹配0次或1次x,倾向于一次 |
x{n,m} | 匹配n或n+1或 ... 或 m次x,倾向于更多 |
x{n, } | 匹配n次或更多次x,倾向于更多 |
x{n} | 明确匹配n次x |
x*? | 匹配0或更多次x,倾向于更少 |
x+? | 匹配1或更多次x,倾向于更少 |
x?? | 匹配0次或1次x,倾向于0次 |
x{n,m}? | 匹配n或 n+1 或 ... 或m次x,倾向于更少 |
x{n, }? | 匹配n次或更多次x,倾向于更少 |
x{n}? | 明确匹配n次x |
分组
符号 | 描述 |
---|---|
(re) | 带编号的捕获分组(用于submatch) |
(?P<name>re) | 命名且带编号的捕获分组(用于submatch) |
(?:re) | 非捕获分组 |
(?flags) | 对当前分组设置flags,非捕获分组 |
(?flags:re) | 为re设置flags,非捕获分组 |
标志(Flags)
Flags语法: xyz(set flags) -xyz(clear flags) xy-z(set xy flags and clear z flags),具体有下列flags:符号 | 描述 |
---|---|
i | 大小写不敏感(default false) |
m | 多行模式:^和$匹配begin/end line以及begin/end text(default false) |
s | 使得.匹配'\n'(default false) |
U | 非贪婪模式:交换 x*和x*?,x+和x+?等语义(default false) |
零宽度字符串
符号 | 描述 |
---|---|
^ | 匹配在文本开始或行开始(flag m = true) |
$ | 匹配文本结束(like \z not Perl's \Z)或文本开始(flag m = true) |
\A | 匹配文本开始 |
\b | 匹配ASCII文本边界(\w on one side,and \W,\A, or \z on the other) |
\B | 排除ASCII文本边界 |
\z | 匹配文本结尾 |
转义序列
符号 | 描述 |
---|---|
\a | 响铃(== \007) |
\f | 换页符(== \014) |
\t | 水平制表符(== \011) |
\n | 换行符(== \012) |
\r | 回车符(== \015) |
\v | 垂直制表符(== \013) |
\* | 字面量*,*用于任何标点字符 |
\123 | 八进制字符代码(最多三位数字) |
\x7F | 十六进制代码(明确两位数字) |
\x{10FFFF} | 十六进制代码 |
\Q...\E | 字面量 ... 即使...中含标点 |
字符集元素
符号 | 描述 |
---|---|
x | 单字符 |
A-Z | 范围内字符(包含A,Z) |
\d | Perl字符集(匹配数字0-9) |
[:foo:] | ASCII字符集foo |
\p{Foo} | Unicode字符集Foo |
\pF | Unicode字符集(一个字符的名字) |
被命名的字符集作字符集元素
符号 | 描述 |
---|---|
[\d] | 匹配数字(== \d) |
[^\d] | 匹配非数字(== \D) |
[\D] | 匹配非数字(== \D) |
[^\D] | 匹配数字(== \d) |
[[:name:]] | 在字符集中匹配被命名的ASCII类(== [:name:]) |
[^[:name:]] | 在字符集中排除被命名的ASCII类(== [:^name:]) |
[\p{Name}] | 在字符集中匹配带命名属性的Unicode字符类(== \p{Name}) |
[^\p{Name}] | 在字符集中排除带命名属性的Unicode字符类(== \P{Name}) |
Perl字符集(ASCII Only)
符号 | 描述 |
---|---|
\d | 匹配数字(== [0-9]) |
\D | 匹配非数字(== [^0-9]) |
\s | 匹配空白字符(== [\t\r\f\n]) |
\S | 匹配非空白字符(== [^\t\r\f\n]) |
\w | 匹配单词字符集(== [0-9a-zA-Z_]) |
\W | 匹配非单词字符集(== [^0-9a-zA-Z]) |
ASCII字符集
符号 | 描述 |
---|---|
[[:alnum:]] | 字母(== [0-9a-zA-Z]) |
[[:alpha:]] | 拼音(== [a-zA-Z]) |
[[:ascii:]] | ASCII([\x00-\x7F]) |
[[:cntrl:]] | 控制符(== [\x00-\x1F\x7F]) |
[[:digit:]] | 数字(== [0-9]) |
[[:graph:]] | 可视化和可打印(== [!-~] == [A-Za-z0-9!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~]) |
[[:lower:]] | 小写(== [a-z]) |
[[:print:]] | 可打印的(== [ -~] == [ [:graph:]]) |
[[:punct:]] | 标点(== [!-/:-@[-`{-~]) |
[[:space:]] | 空白(== [\t\f\r\v\n]) |
[[:upper:]] | 大写(== [A-Z]) |
[[:word:]] | 单词(== [0-9a-zA-Z_]) |
[[:xdigit:]] | 十六进制字符(== [0-9A-Fa-f]) |
Regexp
regexp是go std library提供的一个用于处理正则表达式库,这里接受正则表达式的语法和Python,Perl等其他语言相同,更精确地说,被接受的语法由RE2确定,在 https://golang.org/s/re2syntax中可以找到相关描述,里面的方法用于匹配的方法用正则表达式描述,类似于下面的格式:
Find(All)?(String)?(Submatch)?(Index)?
如果方法中存在'All',则表示会匹配整个表达式全部的非重叠连续区域,当然,存在一个指示参数可选希望获得匹配多少个,反之,则只会得到匹配中的一个
如果方法中存在'String',则表示方法接受的表达式类型是string,返回的匹配结果也是string,否则都是[]byte
如果方法中存在'Submatch',则表示希望方法返回的匹配结果是分组的切片,否则返回整个匹配结果
如果方法中存在'Index',则表示返回匹配结果在原表达式(字符串或byte切片)的位置范围
匹配
为了判断某表达式是否匹配正则表达式格式,go language提供了如下方法:func Match(pattern string, b []byte) (matched bool, err error)
func MatchReader(pattern string, reader io.RuneReader) (matched bool, err error)
func MatchString(pattern string, s string) (matched bool, err error)
Example
package main
import (
"regexp"
"fmt"
)
func checkError(err error) {
if err != nil {
panic(err)
}
}
func main() {
pattern := `[0-9]{6,11}`
tb := []byte("520912345")
matched, err := regexp.Match(pattern, tb)
checkError(err)
fmt.Println(matched) // true
matched, err = regexp.MatchString(pattern, "1977")
checkError(err)
fmt.Println(matched) // false
}
预编译正则表达式
由于一个正则表达式可能会被多次使用,所以go提供了预编译方法,得到一个*Regexp,得到调用其方法的实例,可以重复利用已有的预编译好的正则表达式,并且除了些许配置函数如: Longest()外,Regexp在多个goroutine里是并发安全的func Compile(expr string) (*Regexp, error)
编译解析一个正则表达式并返回,若成功返回,则得到一个正则表达式对象匹配文本当匹配文本时,正则表达式匹配文本最左边(leftmost)开始匹配,并选择回朔搜索最早的匹配结果返回,所谓的leftmost-first匹配语义和Perl,Python等其他实现的方法相同,对于POSIX采用则是
leftmost-longest match,我们可以使用CompilePOSIX声明采用的标准
func CompilePOSIX(expr string) (*Regexp, error)
CompilePOSIX和Compile类似,但是限制正则表达式语法为 POSIX ERE(egrep)且改变匹配语义为leftmost-longest
func MustCompile(expr string) *Regexp
类似于Compile,但是正则表达式解析失败时会发生panic,常用于全局*Regexp的初始化func MustCompilePOSIX(expr string) *Regexp 类似于CompilePOSIX,但是正则表达式解析失败时会发生panic,常用于全局*Regexp的初始化
Example
package main
import (
"regexp"
"fmt"
)
func main() {
re := regexp.MustCompile(`a.*?a`)
text := []byte("aabbaa")
finded := re.FindAll(text, -1)
fmt.Printf("%q\n", finded) //["aa", "aa"]
lre := regexp.MustCompilePOSIX(`a.*?a`) // ["aabbbaa"]
finded = lre.FindAll(text, -1)
fmt.Printf("%q\n", finded)
}
查找
regexp提供了很方便的查找函数,可以通过正则表达式查找文本中符合正则表达式的子序列func (re *Regexp) Find(b []byte) []byte
func (re *Regexp) FindAll(b []byte, n int) [][]byte
func (re *Regexp) FindIndex(b []byte) []int
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
func (re *Regexp) FindSubmatch(b []byte) []byte
func FindSubmatchIndex() []int
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
func (re *Regexp) FindString(str string) string
func (re *Regexp) FindAllString(str string, n int) []string
func (re *Regexp) FindStringIndex(str string) []int
func (re *Regexp) FindAllStringIndex(str string, n int) [][]int
func (re *Regexp) FindStringSubmatch(str string) []string
func (re *Regexp) FindAllStringSubmatch(str string, n int) [][]string
func (re *Regexp) FindStringSubmatchIndex(str string) []int
func (re *Regexp) FindAllStringSubmatchIndex(str string, n int) [][]int
...
package main
import (
"regexp"
"fmt"
)
func main() {
expr := `(\d{4})-(\d{3})-(\d{4})`
text := []byte("1231-456-45631245-123-0012")
re := regexp.MustCompile(expr)
finded := re.Find(text)
fmt.Printf("%s\n", finded)
allfinded := re.FindAll(text, -1)
fmt.Printf("%s\n", allfinded)
submatch := re.FindSubmatch(text)
fmt.Printf("%s\n", submatch)
allsubmatch := re.FindAllSubmatch(text, -1)
fmt.Printf("%s\n", allsubmatch)
index := re.FindIndex(text)
fmt.Printf("%d\n", index)
allindex := re.FindAllIndex(text, -1)
fmt.Printf("%d\n", allindex)
submatchIndex := re.FindSubmatchIndex(text)
fmt.Printf("%d\n", submatchIndex)
allSubmatchIndex := re.FindAllSubmatchIndex(text, -1)
fmt.Printf("%d\n", allSubmatchIndex)
}
package main
import (
"fmt"
"regexp"
)
func main() {
expr := `\d{3}-\d{4}-\d{3}`
text := []byte("123-1234-5671-1234-123")
re := regexp.MustCompile(expr)
/*
实际输出:[123-1234-567]
而这里结果不是 [123-1234-567, 671-1234-123]
是因为"67"匹配后就被"吃"了
*/
all := re.FindAll(text, -1)
fmt.Printf("%s\n", all)
}
替换
我们可以通过一系列函数来实现替换匹配的分组(包括命名分组和编号分组),来重新生成文本(注:生成的文本是先copy源文本再替换副本,并返回副本的),部分API(没Literal,Func)可以使用Expand(占位符)来引用其分组,对于编号分组可以使用$0,$1,$2 ...,命名分组可以使用$name(name为分组名)也可以是${0},${1},${2} ... ${name}解析时是贪婪的,所以$10会被当成${10},要使用$字面量可以使用$$进行转义func (re *Regexp) ReplaceAll(src []byte, repl []byte) []byte
func (re *Regexp) ReplaceAllString(src string, repl string) string
func (re *Regexp) ReplaceFunc(src []byte, repl func([]byte) []byte) []byte
func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string
func (re *Regexp) ReplaceAllLiteral(src []byte, repl []byte) []byte
func (re *Regexp) ReplaceAllStringLiteral(src string, repl string) string
...
Example
package main
import (
"fmt"
"regexp"
"bytes"
)
func main() {
expr := `\b(?P<begin>\w+)\b \b(\w+)\b \b(?P<end>\w+)\b`
text := []byte("Hello Marco Epsilon")
re := regexp.MustCompile(expr)
replace := []byte("$end $2 $begin")
replaced := re.ReplaceAll(text, replace)
// Output: Epsilon Marco Hello
fmt.Printf("%s\n", replaced)
// add func to lowercase first alpha,without using expand
funced := re.ReplaceAllFunc(text, func(repl []byte) []byte {
return bytes.ToLower(repl)
})
// Output: hello marco epsilon
fmt.Printf("%s\n", funced)
// keep $3 $2 $1 because of literal,without using expand
literaled := re.ReplaceAllLiteral(text, []byte("$3 $2 $1"))
fmt.Printf("%s\n", literaled)
}
组合
除了上面一系列的方法,还有专门用于精确替换和组合特定分组序列的方法func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte
func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte
package main
import (
"fmt"
"regexp"
)
func main() {
expr := `(?m)(?P<key>\w+):\s(?P<value>\w+)$`
re := regexp.MustCompile(expr)
text := []byte(`
optional1: content1
optional2: content2
optional3: content3
`)
dst := []byte{}
template := []byte("$key=$value\n")
allIndex := re.FindAllSubmatchIndex(text, -1)
for _, index := range allIndex {
dst = re.Expand(dst, template, text, index)
}
/*
Output:
optional1=content1
optional2=content2
optional3=content3
*/
fmt.Printf("%s", dst)
}
More Functions
package main
import (
"regexp"
"fmt"
)
func main() {
expr := `hello,this is literal,next is not\d{3}`
re := regexp.MustCompile(expr)
literal, completed := re.LiteralPrefix()
// Output:
// hello,this is literal,next is not
// false
fmt.Println(literal)
fmt.Println(completed)
}
package main
import (
"fmt"
"regexp"
)
func main() {
// leftmost-first mode
expr := `(?U)a.*b`
text := []byte("abaaaab")
re := regexp.MustCompile(expr)
// Output:
// [ab aaaab]
fmt.Printf("%s\n", re.FindAll(text, -1))
// leftmost-longest mode
re.Longest()
// Output:
// [abaaaab]
fmt.Printf("%s\n", re.FindAll(text, -1))
}
package main
import (
"fmt"
"regexp"
)
func main() {
expr := `((\d{3})haha\d{2}){4}`
re := regexp.MustCompile(expr)
// Output:
// 2
fmt.Println(re.NumSubexp())
}
n > 0
返回最多n个子串,最后一个子串是未分割的剩余字符串n = 0
结果将会是niln < 0
返回所有子字符串package main
import (
"fmt"
"regexp"
)
func main() {
expr := `ab|bc`
re := regexp.MustCompile(expr)
text := "cbabcbdebacbcabcbbacb"
results := re.Split(text, -1)
for _, value := range results {
fmt.Println(value)
}
fmt.Println("-----------")
results = re.Split(text, 3)
for _, value := range results {
fmt.Println(value)
}
// Output:
/*
cb
cbdebac
cbbacb
-----------
cb
cbdebac
abcbbacb
*/
}
package main
import (
"regexp"
"fmt"
)
func main() {
expr := `ab|bc\d{2,5}`
re := regexp.MustCompile(expr)
fmt.Println(re.String())
//Output: ab|bc\d{2,5}
}
package main
import (
"regexp"
"fmt"
)
func main() {
expr := `(?P<name>[^:]+):\s+(.*),\s+(?P<age>[^:]+):\s+(.*)`
re := regexp.MustCompile(expr)
names := re.SubexpNames()
for _, v := range names {
fmt.Println(v)
}
//Output:
/*
name
age
*/
}