Skip to main content

函数库:re - 正则表达式

正则表达式(Regular Expression),也被称为“Regex”或“RegExp”,是一种用于描述字符串模式的文本模式。正则表达式通常由一系列字符和特殊字符组成,用于匹配或查找其他字符串中的模式。

正则表达式常用于文本处理和搜索操作中,例如在搜索引擎中,用户可以使用正则表达式搜索特定的网页;在编程中,开发人员可以使用正则表达式来验证、提取和转换文本数据。

正则表达式语法包含以下元素:

  1. 字面量字符:这些是单个字符,如字母、数字、标点符号等,它们直接匹配文本字符串中的相应字符。

  2. 特殊字符:这些是在正则表达式中具有特殊含义的字符,如元字符、字符类、分组等,它们用于匹配特定的文本模式。

  3. 量词:这些用于指定匹配特定字符或字符类的次数。例如,*表示匹配前面的字符零次或多次,+表示匹配一次或多次,?表示匹配零次或一次。

正则表达式可以在多种编程语言和工具中使用。在使用正则表达式时,需要仔细考虑模式的要求,以确保正则表达式可以准确匹配所需的模式。

在 Yaklang 中,re 是基础正则表达式的模块,提供了各种各样的正则表达式函数方便用户完成如下功能

  1. 基础的编译型正则使用接口,可匹配,提取数据;
  2. Grok 风格的正则处理
  3. 预设正则:提取 IP,Email,Path,URL 等
  4. 快捷提取数据于定位特征字符串,支持分组提取
  5. 快速根据正则替代特征字符串
tip

Grok 方式常用于 Nginx/Apache 等日志中关键信息提取。

原生正则库

yak 完全兼容原生 Golang 正则库,原始的 Golang 正则的用法在 Yak 中均可使用。

与此同时,Yak 实现了基于原正则库的一些封装,让基本操作变得更容易让人使用。

快速正则匹配数据#

yak 支持的正则表达式写法很多,有忠诚于标准库的写法,同时有高度封装的更易用的写法

我们这篇文章将介绍作者最推荐的几种文本匹配的写法

func str.MatchAllOfGlob(origin, globPattern1, globPattern2) return boolfunc str.MatchAnyOfGlob(origin, globPattern1, globPattern2) return bool
func str.MatchAllOfRegexp(origin, rePattern1, rePattern2) return boolfunc str.MatchAnyOfRegexp(origin, rePattern1, rePattern2) return bool
func str.MatchAllOfSubString(origin, sub1, sub2) return boolfunc str.MatchAnyOfSubString(origin, sub1, sub2) return bool

这六个函数可以极大方便大家进行规则的编写,可以分别支持对 glob 语法 regexp 语法,以及子字符串匹配。

All 的逻辑是说,所有的规则都应该被匹配,才会返回 true,否则返回 false

Any 的逻辑是说,至少一条规则被匹配,将会返回 true,否则返回 false,如果没有规则,则返回 false

单个正则匹配文本#

result = str.MatchAllOfRegexp(`GET / HTTP/1.1Host: www.baidu.com
`, `Host: [w]{3}\.baidu\.com`)if result {    println("matched")}

在这个场景下,上述规则可以等同于 re.Match

多正则同时用于匹配文档#

多个正则同时用于匹配一个文档时,通常会有 AND / OR 两种情况。 为此,我们稍微改一下上面的代码,让它变成匹配两个正则,都匹配才可以返回 true

多正则匹配:AND 场景#

data = `GET / HTTP/1.1Host: www.baidu.com
`result = str.MatchAllOfRegexp(data, `Host: [w]{3}\.baidu\.com`, `HTTP\/1\.1`)if result {    println("matched")}

多正则匹配:OR 场景#

data = `GET / HTTP/1.1Host: www.baidu.com
`result = str.MatchAnyOfRegexp(data, `Host: [w]{3}\.baidu\.com`, `HTTP\/1\.1`)if result {    println("matched")}

使用正则提取数据#

如下几个接口在 >= 1.0.13-sp14 引擎中使用:

// 匹配符合正则要求的结果(只返回一个结果)func re.Find(data, regexp/*string*/) return string
// 匹配符合正则要求的结果(返回多个结果)func re.FindAll(data, regexp/*string*/) return []string
// 匹配符合正则要求的匹配结果的位置,返回结果为数组,数组两个元素,第一个元素为起始位置,另一个为结束位置func re.FindIndex(data, regexp/*string*/) return []int
// 匹配符合正则的所有结果,包含分组func re.FindSubmatch(data, regexp/*string*/) return string
// 匹配符合正则的所有结果(起止位置),包含分组func re.FindSubmatchIndex(data, regexp/*string*/) return string
// 匹配所有符合正则的所有结果,包含分组func re.FindAllSubmatch(data, regexp/*string*/) return string
// 匹配所有符合正则的所有结果(起止位置),包含分组func re.FindAllSubmatchIndex(data, regexp/*string*/) return
共性

这几个函数本质上是对 Golang regexp 标准库的封装,在使用的过程中要注意,不支持 re2 的回溯语法等高级语法。

这些函数第一个参数为原始数据,[]bytestring 都可以被接受,第二个参数为正则字符串。

如果正则编译错误,不出意外,用户将会看到 warning 日志。

可以查看如下案例使用该内容

提取单个结果#

data = `.796 Electron Helper (Renderer)[7878:58760] CoreText note: Client requested name ".NewYork-Regular", it will get Times-Roman rather than the intended font. All systCoreText aaaa:em UI font acce`
// 匹配提取单个结果dump(re.Find(data, `CoreText\s[^:]+`))dump(re.FindIndex(data, `CoreText\s[^:]+`))
/*OUTPUT:    (string) (len=13) "CoreText note"    ([]int) (len=2 cap=2) {        (int) 44,        (int) 57    }*/

提取所有结果#

data = `.796 Electron Helper (Renderer)[7878:58760] CoreText note: Client requested name ".NewYork-Regular", it will get Times-Roman rather than the intended font. All systCoreText aaaa:em UI font acce`
// 匹配提取所有结果dump(re.FindAll(data, `CoreText\s[^:]+`))dump(re.FindAllIndex(data, `CoreText\s[^:]+`))/*OUTPUT:    ([]string) (len=2 cap=10) {        (string) (len=13) "CoreText note",        (string) (len=13) "CoreText aaaa"    }    ([][]int) (len=2 cap=10) {        ([]int) (len=2 cap=2) {            (int) 44,            (int) 57        },        ([]int) (len=2 cap=2) {            (int) 164,            (int) 177        }    }*/

提取包含分组的结果#

data = `.796 Electron Helper (Renderer)[7878:58760] CoreText note: Client requested name ".NewYork-Regular", it will get Times-Roman rather than the intended font. All systCoreText aaaa:em UI font acce`
// 匹配提取分组结果(单个)dump(re.FindSubmatch(data, `CoreText\s([^:]+)`))dump(re.FindSubmatchIndex(data, `CoreText\s([^:]+)`))/*OUTPUT:([]string) (len=2 cap=2) {    (string) (len=13) "CoreText note",    (string) (len=4) "note"}([]int) (len=4 cap=4) {    (int) 44,    (int) 57,    (int) 53,    (int) 57}*/

提取全部分组结果#

data = `.796 Electron Helper (Renderer)[7878:58760] CoreText note: Client requested name ".NewYork-Regular", it will get Times-Roman rather than the intended font. All systCoreText aaaa:em UI font acce`
// 匹配提取分组结果(多个)dump(re.FindSubmatchAll(data, `CoreText\s([^:]+)`))dump(re.FindSubmatchAllIndex(data, `CoreText\s([^:]+)`))
/*OUTPUT:
([][]string) (len=2 cap=10) { ([]string) (len=2 cap=2) {  (string) (len=13) "CoreText note",  (string) (len=4) "note" }, ([]string) (len=2 cap=2) {  (string) (len=13) "CoreText aaaa",  (string) (len=4) "aaaa" }}([][]int) (len=2 cap=10) { ([]int) (len=4 cap=4) {  (int) 44,  (int) 57,  (int) 53,  (int) 57 }, ([]int) (len=4 cap=4) {  (int) 164,  (int) 177,  (int) 173,  (int) 177 }}*/

根据正则替换数据#

data = `.796 Electron Helper (Renderer)[7878:58760] CoreText note: Client requested name ".NewYork-Regular", it will get Times-Roman rather than the intended font. All systCoreText aaaa:em UI font acce`pattern := `CoreText\s([^:]+)`
// 替换正则匹配结果(全部)dump(re.ReplaceAll(data, pattern, "__abcabcabc__"))
// ReplaceAllWithFunc 的替换函数可以接受一个字符串作为输入,输出一个替换后的字符串dump(re.ReplaceAllWithFunc(data, pattern, func(i){return codec.Md5(i)}))
/*OUTPUT:
(string) (len=193) ".796 Electron Helper (Renderer)[7878:58760] __abcabcabc__: Client requested name \".NewYork-Regular\", it will get Times-Roman rather than the intended font. All syst__abcabcabc__:em UI font acce"(string) (len=231) ".796 Electron Helper (Renderer)[7878:58760] 85f5a5815371ce7378545da6415a4a00: Client requested name \".NewYork-Regular\", it will get Times-Roman rather than the intended font. All systc4a9957ab07eca49d3691ce4bbf92a57:em UI font acce"
*/

原汁原味的 Golang regexp#

data = `.796 Electron Helper (Renderer)[7878:58760] CoreText note: Client requested name ".NewYork-Regular", it will get Times-Roman rather than the intended font. All systCoreText aaaa:em UI font acce`
// 匹配提取分组结果(多个)r, err = re.Compile(`CoreText\s([^:]+)`)die(err)
dump(r.FindAllStringSubmatch(data, -1))dump(r.FindAllStringSubmatchIndex(data, -1))
/*OUTPUT:
([]string) (len=2 cap=2) { (string) (len=13) "CoreText note", (string) (len=4) "note"}([]int) (len=4 cap=4) { (int) 44, (int) 57, (int) 53, (int) 57}([][]string) (len=2 cap=10) { ([]string) (len=2 cap=2) {  (string) (len=13) "CoreText note",  (string) (len=4) "note" }, ([]string) (len=2 cap=2) {  (string) (len=13) "CoreText aaaa",  (string) (len=4) "aaaa" }}([][]int) (len=2 cap=10) { ([]int) (len=4 cap=4) {  (int) 44,  (int) 57,  (int) 53,  (int) 57 }, ([]int) (len=4 cap=4) {  (int) 164,  (int) 177,  (int) 173,  (int) 177 }}*/
Golang regexp 模块的所有功能均可兼容

使用 re 模块可以编译/执行 Golang 的 Compile/MustCompile 对象:

type regexp.(Regexp) struct {  Fields(可用字段):   StructMethods(结构方法/函数):  PtrStructMethods(指针结构方法/函数):      func Copy() return(*regexp.Regexp)      func Expand(v1: []uint8, v2: []uint8, v3: []uint8, v4: []int) return([]uint8)      func ExpandString(v1: []uint8, v2: string, v3: string, v4: []int) return([]uint8)      func Find(v1: []uint8) return([]uint8)      func FindAll(v1: []uint8, v2: int) return([][]uint8)      func FindAllIndex(v1: []uint8, v2: int) return([][]int)      func FindAllString(v1: string, v2: int) return([]string)      func FindAllStringIndex(v1: string, v2: int) return([][]int)      func FindAllStringSubmatch(v1: string, v2: int) return([][]string)      func FindAllStringSubmatchIndex(v1: string, v2: int) return([][]int)      func FindAllSubmatch(v1: []uint8, v2: int) return([][][]uint8)      func FindAllSubmatchIndex(v1: []uint8, v2: int) return([][]int)      func FindIndex(v1: []uint8) return([]int)      func FindReaderIndex(v1: io.RuneReader) return([]int)      func FindReaderSubmatchIndex(v1: io.RuneReader) return([]int)      func FindString(v1: string) return(string)      func FindStringIndex(v1: string) return([]int)      func FindStringSubmatch(v1: string) return([]string)      func FindStringSubmatchIndex(v1: string) return([]int)      func FindSubmatch(v1: []uint8) return([][]uint8)      func FindSubmatchIndex(v1: []uint8) return([]int)      func LiteralPrefix() return(string, bool)      func Longest()      func Match(v1: []uint8) return(bool)      func MatchReader(v1: io.RuneReader) return(bool)      func MatchString(v1: string) return(bool)      func NumSubexp() return(int)      func ReplaceAll(v1: []uint8, v2: []uint8) return([]uint8)      func ReplaceAllFunc(v1: []uint8, v2: func (v1: []uint8) return([]uint8) ) return([]uint8)      func ReplaceAllLiteral(v1: []uint8, v2: []uint8) return([]uint8)      func ReplaceAllLiteralString(v1: string, v2: string) return(string)      func ReplaceAllString(v1: string, v2: string) return(string)      func ReplaceAllStringFunc(v1: string, v2: func (v1: string) return(string) ) return(string)      func Split(v1: string, v2: int) return([]string)      func String() return(string)      func SubexpIndex(v1: string) return(int)      func SubexpNames() return([]string)}