Go内置了对正则表达式的支持,这里是一般的正则表达式常规用法的例子。
package main import "bytes" import "fmt" import "regexp" func main() { // 测试模式是否匹配字符串,括号里面的意思是 // 至少有一个a-z之间的字符存在 match, _ := regexp.MatchString("p([a-z]+)ch", "peach") fmt.Println(match) //true // 上面我们直接使用了字符串匹配的正则表达式, // 但是对于其他的正则匹配任务,你需要使用 // `Compile`来使用一个优化过的正则对象 r, _ := regexp.Compile("p([a-z]+)ch") // 正则结构体对象有很多方法可以使用,比如上面的例子 // 也可以像下面这么写 fmt.Println(r.MatchString("peach")) //true // 这个方法检测字符串参数是否存在正则所约束的匹配 fmt.Println(r.FindString("pea1ch pun1ch")) // // 这个方法查找第一次匹配的索引,并返回匹配字符串 // 的起始索引和结束索引,而不是匹配的字符串 fmt.Println(r.FindStringIndex("pe2ach pu1nch")) //[] // 这个方法返回全局匹配的字符串和局部匹配的字符,比如 // 这里会返回匹配`p([a-z]+)ch`的字符串 // 和匹配`([a-z]+)`的字符串 fmt.Println(r.FindStringSubmatch("peach punch")) //[peach ea] // 和上面的方法一样,不同的是返回全局匹配和局部匹配的 // 起始索引和结束索引 fmt.Println(r.FindStringSubmatchIndex("peach punch")) //[0 5 1 3] // 这个方法返回所有正则匹配的字符,不仅仅是第一个 fmt.Println(r.FindAllString("peach punch pinch", -1)) //[peach punch pinch] // 这个方法返回所有全局匹配和局部匹配的字符串起始索引 // 和结束索引 fmt.Println(r.FindAllStringSubmatchIndex("peach punch pinch", -1)) //[[0 5 1 3] [6 11 7 9] [12 17 13 15]] // 为这个方法提供一个正整数参数来限制匹配数量 fmt.Println(r.FindAllString("peach punch pinch", 2)) //[peach punch] //上面我们都是用了诸如`MatchString`这样的方法,其实 // 我们也可以使用`[]byte`作为参数,并且使用`Match` // 这样的方法名 fmt.Println(r.Match([]byte("peach"))) //true // 当使用正则表达式来创建常量的时候,你可以使用`MustCompile` // 因为`Compile`返回两个值 r = regexp.MustCompile("p([a-z]+)ch") fmt.Println(r) //p([a-z]+)ch // regexp包也可以用来将字符串的一部分替换为其他的值 fmt.Println(r.ReplaceAllString("a peach", "<fruit>")) //a <fruit> // `Func`变量可以让你将所有匹配的字符串都经过该函数处理 // 转变为所需要的值 in := []byte("a peach") out := r.ReplaceAllFunc(in, bytes.ToUpper) fmt.Println(string(out)) //a PEACH }
下面是另外一个示例
------------------------------------------------------------ Golang中的正则表达式 ------------------------------------------------------------ 用法: ------------------------------ 单一: . 匹配任意一个字符,如果设置 s = true,则可以匹配换行符 [字符类] 匹配“字符类”中的一个字符,“字符类”见后面的说明 [^字符类] 匹配“字符类”外的一个字符,“字符类”见后面的说明 \小写Perl标记 匹配“Perl类”中的一个字符,“Perl类”见后面的说明 \大写Perl标记 匹配“Perl类”外的一个字符,“Perl类”见后面的说明 [:ASCII类名:] 匹配“ASCII类”中的一个字符,“ASCII类”见后面的说明 [:^ASCII类名:] 匹配“ASCII类”外的一个字符,“ASCII类”见后面的说明 \pUnicode普通类名 匹配“Unicode类”中的一个字符(仅普通类),“Unicode类”见后面的说明 \PUnicode普通类名 匹配“Unicode类”外的一个字符(仅普通类),“Unicode类”见后面的说明 \p{Unicode类名} 匹配“Unicode类”中的一个字符,“Unicode类”见后面的说明 \P{Unicode类名} 匹配“Unicode类”外的一个字符,“Unicode类”见后面的说明 ------------------------------ 复合: xy 匹配 xy(x 后面跟随 y) x|y 匹配 x 或 y (优先匹配 x) ------------------------------ 重复: x* 匹配零个或多个 x,优先匹配更多(贪婪) x+ 匹配一个或多个 x,优先匹配更多(贪婪) x? 匹配零个或一个 x,优先匹配一个(贪婪) x{n,m} 匹配 n 到 m 个 x,优先匹配更多(贪婪) x{n,} 匹配 n 个或多个 x,优先匹配更多(贪婪) x{n} 只匹配 n 个 x x*? 匹配零个或多个 x,优先匹配更少(非贪婪) x+? 匹配一个或多个 x,优先匹配更少(非贪婪) x?? 匹配零个或一个 x,优先匹配零个(非贪婪) x{n,m}? 匹配 n 到 m 个 x,优先匹配更少(非贪婪) x{n,}? 匹配 n 个或多个 x,优先匹配更少(非贪婪) x{n}? 只匹配 n 个 x ------------------------------ 分组: (子表达式) 被捕获的组,该组被编号 (子匹配) (?P<命名>子表达式) 被捕获的组,该组被编号且被命名 (子匹配) (?:子表达式) 非捕获的组 (子匹配) (?标记) 在组内设置标记,非捕获,标记影响当前组后的正则表达式 (?标记:子表达式) 在组内设置标记,非捕获,标记影响当前组内的子表达式 标记的语法是: xyz (设置 xyz 标记) -xyz (清除 xyz 标记) xy-z (设置 xy 标记, 清除 z 标记) 可以设置的标记有: i 不区分大小写 (默认为 false) m 多行模式:让 ^ 和 $ 匹配整个文本的开头和结尾,而非行首和行尾(默认为 false) s 让 . 匹配 \n (默认为 false) U 非贪婪模式:交换 x* 和 x*? 等的含义 (默认为 false) ------------------------------ 位置标记: ^ 如果标记 m=true 则匹配行首,否则匹配整个文本的开头(m 默认为 false) $ 如果标记 m=true 则匹配行尾,否则匹配整个文本的结尾(m 默认为 false) \A 匹配整个文本的开头,忽略 m 标记 \b 匹配单词边界 \B 匹配非单词边界 \z 匹配整个文本的结尾,忽略 m 标记 ------------------------------ 转义序列: \a 匹配响铃符 (相当于 \x07) 注意:正则表达式中不能使用 \b 匹配退格符,因为 \b 被用来匹配单词边界, 可以使用 \x08 表示退格符。 \f 匹配换页符 (相当于 \x0C) \t 匹配横向制表符(相当于 \x09) \n 匹配换行符 (相当于 \x0A) \r 匹配回车符 (相当于 \x0D) \v 匹配纵向制表符(相当于 \x0B) \123 匹配 8 進制编码所代表的字符(必须是 3 位数字) \x7F 匹配 16 進制编码所代表的字符(必须是 3 位数字) \x{10FFFF} 匹配 16 進制编码所代表的字符(最大值 10FFFF ) \Q...\E 匹配 \Q 和 \E 之间的文本,忽略文本中的正则语法 \\ 匹配字符 \ \^ 匹配字符 ^ \$ 匹配字符 $ \. 匹配字符 . \* 匹配字符 * \+ 匹配字符 + \? 匹配字符 ? \{ 匹配字符 { \} 匹配字符 } \( 匹配字符 ( \) 匹配字符 ) \[ 匹配字符 [ \] 匹配字符 ] \| 匹配字符 | ------------------------------ 可以将“命名字符类”作为“字符类”的元素: [\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类”、“Unicode类”): x 单个字符 A-Z 字符范围(包含首尾字符) \小写字母 Perl类 [:ASCII类名:] ASCII类 \p{Unicode脚本类名} Unicode类 (脚本类) \pUnicode普通类名 Unicode类 (普通类) ------------------------------ “Perl 类”取值如下: \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_]) ------------------------------ “ASCII 类”取值如下 [:alnum:] 字母数字 (相当于 [0-9A-Za-z]) [:alpha:] 字母 (相当于 [A-Za-z]) [:ascii:] ASCII 字符集 (相当于 [\x00-\x7F]) [:blank:] 空白占位符 (相当于 [\t ]) [:cntrl:] 控制字符 (相当于 [\x00-\x1F\x7F]) [:digit:] 数字 (相当于 [0-9]) [:graph:] 图形字符 (相当于 [!-~]) [:lower:] 小写字母 (相当于 [a-z]) [:print:] 可打印字符 (相当于 [ -~] 相当于 [ [:graph:]]) [:punct:] 标点符号 (相当于 [!-/:-@[-反引号{-~]) [:space:] 空白字符(相当于 [\t\n\v\f\r ]) [:upper:] 大写字母(相当于 [A-Z]) [:word:] 单词字符(相当于 [0-9A-Za-z_]) [:xdigit:] 16 進制字符集(相当于 [0-9A-Fa-f]) ------------------------------------------------------------ 注意: 对于 [a-z] 这样的正则表达式,如果要在 [] 中匹配 - ,可以将 - 放在 [] 的开头或结尾,例如 [-a-z] 或 [a-z-] 可以在 [] 中使用转义字符:\f、\t、\n、\r、\v、\377、\xFF、\x{10FFFF}、\\、\^、\$、\.、\*、\+、\?、\{、\}、\(、\)、\[、\]、\|(具体含义见上面的说明) 如果在正则表达式中使用了分组,则在执行正则替换的时候,“替换内容”中可以使用 $1、${1}、$name、${name} 这样的“分组引用符”获取相应的分组内容。 其中 $0 代表整个匹配项,$1 代表第 1 个分组,$2 代表第 2 个分组,……。 如果“分组引用符”是 $name 的形式,则在解析的时候,name 是取尽可能长的字符串,比如:$1x 相当于 ${1x},而不是${1}x,再比如:$10 相当于 ${10}, 而不是 ${1}0。 由于 $ 字符会被转义,所以要在“替换内容”中使用 $ 字符,可以用 \$ 代替。 上面介绍的正则表达式语法是“Perl 语法”,除了“Perl 语法”外,Go 语言中还有另一种“POSIX 语法”,“POSIX 语法”除了不能使用“Perl 类”之外,其它都一样。 ------------------------------------------------------------ // 示例 func main() { text := `Hello 世界!123 Go.` // 查找连续的小写字母 reg := regexp.MustCompile(`[a-z]+`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["ello" "o"] // 查找连续的非小写字母 reg = regexp.MustCompile(`[^a-z]+`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["H" " 世界!123 G" "."] // 查找连续的单词字母 reg = regexp.MustCompile(`[\w]+`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello" "123" "Go"] // 查找连续的非单词字母、非空白字符 reg = regexp.MustCompile(`[^\w\s]+`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["世界!" "."] // 查找连续的大写字母 reg = regexp.MustCompile(`[[:upper:]]+`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["H" "G"] // 查找连续的非 ASCII 字符 reg = regexp.MustCompile(`[[:^ascii:]]+`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["世界!"] // 查找连续的标点符号 reg = regexp.MustCompile(`[\pP]+`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["!" "."] // 查找连续的非标点符号字符 reg = regexp.MustCompile(`[\PP]+`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello 世界" "123 Go"] // 查找连续的汉字 reg = regexp.MustCompile(`[\p{Han}]+`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["世界"] // 查找连续的非汉字字符 reg = regexp.MustCompile(`[\P{Han}]+`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello " "!123 Go."] // 查找 Hello 或 Go reg = regexp.MustCompile(`Hello|Go`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello" "Go"] // 查找行首以 H 开头,以空格结尾的字符串 reg = regexp.MustCompile(`^H.*\s`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello 世界!123 "] // 查找行首以 H 开头,以空白结尾的字符串(非贪婪模式) reg = regexp.MustCompile(`(?U)^H.*\s`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello "] // 查找以 hello 开头(忽略大小写),以 Go 结尾的字符串 reg = regexp.MustCompile(`(?i:^hello).*Go`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello 世界!123 Go"] // 查找 Go. reg = regexp.MustCompile(`\QGo.\E`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Go."] // 查找从行首开始,以空格结尾的字符串(非贪婪模式) reg = regexp.MustCompile(`(?U)^.* `) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello "] // 查找以空格开头,到行尾结束,中间不包含空格字符串 reg = regexp.MustCompile(` [^ ]*$`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // [" Go."] // 查找“单词边界”之间的字符串 reg = regexp.MustCompile(`(?U)\b.+\b`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello" " 世界!" "123" " " "Go"] // 查找连续 1 次到 4 次的非空格字符,并以 o 结尾的字符串 reg = regexp.MustCompile(`[^ ]{1,4}o`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello" "Go"] // 查找 Hello 或 Go reg = regexp.MustCompile(`(?:Hell|G)o`) fmt.Printf("%q\n", reg.FindAllString(text, -1)) // ["Hello" "Go"] // 查找 Hello 或 Go,替换为 Hellooo、Gooo reg = regexp.MustCompile(`(Hell|G)o`) fmt.Printf("%q\n", reg.ReplaceAllString(text, "${1}ooo")) // "Hellooo 世界!123 Gooo." // 交换 Hello 和 Go reg = regexp.MustCompile(`(Hello)(.*)(Go)`) fmt.Printf("%q\n", reg.ReplaceAllString(text, "$3$2$1")) // "Go 世界!123 Hello." // 特殊字符的查找 reg = regexp.MustCompile(`[\f\t\n\r\v\123\x7F\x{10FFFF}\\\^\$\.\*\+\?\{\}\(\)\[\]\|]`) fmt.Printf("%q\n", reg.ReplaceAllString("\f\t\n\r\v\123\x7F\U0010FFFF\\^$.*+?{}()[]|", "-")) // "----------------------" } ------------------------------------------------------------
–
正则中有分组这个功能,在golang中也可以使用命名分组。
一次匹配的情况
场景还原如下:
有一行文本,格式为:姓名 年龄 邮箱地址
请将其转换为一个map
代码实现如下:
str := `Alice 20 alice@gmail.com` // 使用命名分组,显得更清晰 re := regexp.MustCompile(`(?P<name>[a-zA-Z]+)\s+(?P<age>\d+)\s+(?P<email>\w+@\w+(?:\.\w+)+)`) match := re.FindStringSubmatch(str) groupNames := re.SubexpNames() fmt.Printf("%v, %v, %d, %d\n", match, groupNames, len(match), len(groupNames)) result := make(map[string]string) // 转换为map for i, name := range groupNames { if i != 0 && name != "" { // 第一个分组为空(也就是整个匹配) result[name] = match[i] } } prettyResult, _ := json.MarshalIndent(result, "", " ") fmt.Printf("%s\n", prettyResult)
输出为:
[Alice 20 alice@gmail.com Alice 20 alice@gmail.com], [ name age email], 4, 4 { "age": "20", "email": "alice@gmail.com", "name": "Alice" }
注意 [ name age email]有4个元素, 第一个为""。
多次匹配的情况
接上面的例子,实现一个更贴近现实的需求:
有一个文件, 内容大致如下:
Alice 20 alice@gmail.com
Bob 25 bob@outlook.com
gerrylon 26 gerrylon@github.com
…
更多内容
和上面一样, 不过这次转出来是一个slice of map, 也就是多个map。
代码如下:
// 文件内容直接用字符串表示 usersStr := ` Alice 20 alice@gmail.com Bob 25 bob@outlook.com gerrylon 26 gerrylon@github.com ` userRe := regexp.MustCompile(`(?P<name>[a-zA-Z]+)\s+(?P<age>\d+)\s+(?P<email>\w+@\w+(?:\.\w+)+)`) // 这里要用FindAllStringSubmatch,找到所有的匹配 users := userRe.FindAllStringSubmatch(usersStr, -1) groupNames := userRe.SubexpNames() var result []map[string]string // slice of map // 循环所有行 for _, user := range users { m := make(map[string]string) // 对每一行生成一个map for j, name := range groupNames { if j != 0 && name != "" { m[name] = strings.TrimSpace(user[j]) } } result = append(result, m) } prettyResult, _ := json.MarshalIndent(result, "", " ") fmt.Println(string(prettyResult))
输出为:
[ { "age": "20", "email": "alice@gmail.com", "name": "Alice" }, { "age": "25", "email": "bob@outlook.com", "name": "Bob" }, { "age": "26", "email": "gerrylon@github.com", "name": "gerrylon" } ]
总结
使用命名分组可以使正则表示的意义更清晰。
转换为map更加符合人类的阅读习惯,不过比一般的根据索引取分组值麻烦一些。
–
下面是另外一个分组示例
package main import ( "fmt" "regexp" ) func main() { var rows [2]string rows[0] = `<a href="access-20200319.log">access-20200319.log</a> 19-Mar-2020 09:23 0` rows[1] = `<a href="server-20200319.log">server-20200319.log</a> 19-Mar-2020 09:23 444` for _, row := range rows { // log re := regexp.MustCompile(`>(?P<name>.*)</a.*\s(?P<size>\d+)$`) match := re.FindStringSubmatch(row) for _, i := range match { fmt.Println("log ", i) } } }
输出
log >access-20200319.log</a> 19-Mar-2020 09:23 0 log access-20200319.log log 0 log >server-20200319.log</a> 19-Mar-2020 09:23 444 log server-20200319.log log 444
参考:https://www.kancloud.cn/itfanr/go-by-example/81710
https://www.cnblogs.com/golove/p/3269099.html
https://blog.csdn.net/butterfly5211314/article/details/82532970
–
–
评论前必须登录!
注册