路漫漫其修远兮
吾将上下而求索

go学习:正则

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

未经允许不得转载:江哥架构师笔记 » go学习:正则

分享到:更多 ()

评论 抢沙发

评论前必须登录!