Golang_18: Go语言 正则表达式

原文链接:https://xiets.blog.csdn.net/article/details/130866749

版权声明:原创文章禁止转载

专栏目录:Golang 专栏(总目录)

Go 语言 正则表达式 处理使用内置的 regexp 模块。

regexp 包内主要的类型和函数:

// 表示一个预编译的正则表达式
type Regexp struct {
    // 没有导出属性
}


// 判断给定的 []byte 是否是否包含匹配 pattern 正则表达式的子文本
func Match(pattern string, b []byte) (matched bool, err error)

// 从 reader 中读取 []byte, 然后再判断是否匹配 pattern
func MatchReader(pattern string, r io.RuneReader) (matched bool, err error)

// 判断给定的 string 是否是否包含匹配 pattern 正则表达式的子字符串
func MatchString(pattern string, s string) (matched bool, err error)

// 把指定字符串中的所有 正则表达式元字符 加上转义符号并返回, 返回的字符串是与原文本匹配的正则表达式。
func QuoteMeta(s string) string


// 编译正则表达式, 返回 正则表达式对象 和 错误。
func Compile(expr string) (*Regexp, error)

// 类似于 Compile(), 但如果无法解析表达式, 则会引发宕机。它简化了保存已编译正则表达式作为全局变量的安全初始化。
func MustCompile(str string) *Regexp

// 类似于 Compile(), 但将正则表达式限制为 POSIX ERE (egrep) 语法并将匹配语义更改为最左最长。
func CompilePOSIX(expr string) (*Regexp, error)

// 类似于 CompilePOSIX(), 但如果无法解析表达式, 则会引发宕机。它简化了保存已编译正则表达式作为全局变量的安全初始化。
func MustCompilePOSIX(str string) *Regexp

1. 匹配字符串

判断文本字符串是否匹配指定正则表达式规则:

package main

import (
	"bytes"
	"fmt"
	"regexp"
)

func main() {
	matched, err := regexp.Match("^[0-9]+$", []byte("012345"))
	fmt.Println(matched, err)  // true <nil>

	matched, err = regexp.Match("^[1-9][0-9]+$", []byte("012345"))
	fmt.Println(matched, err)  // false <nil>

	matched, err = regexp.MatchString("^[1-9]+.*", "123456abc")
	fmt.Println(matched, err)  // true <nil>

	matched, err = regexp.MatchString("abc", "123abc456")
	fmt.Println(matched, err)  // true <nil>

	r := bytes.NewReader([]byte("abc"))
	matched, err = regexp.MatchReader("^[a-z]+$", r)
	fmt.Println(matched, err)  // true <nil>

	s := "^[a-z]+ab(c)?.*$"
	quotePattern := regexp.QuoteMeta(s)
	fmt.Println(quotePattern)  // \^\[a-z\]\+ab\(c\)\?\.\*\$
	matched, err = regexp.MatchString(quotePattern, s)
	fmt.Println(matched, err)  // true <nil>
	
	// 以上快速判断匹配的方法内部也是先编译正则表达式再匹配
}

2. 正则表达式对象: regexp.Regexp

regexp.Regexp 表示一个正则表达式对象。

regexp.Regexp 的 匹配方法:

// 判断指定 文本/字符串 中是否有匹配 正则表达式(re) 的 子文本/字符串
func (re *Regexp) Match(b []byte) bool
func (re *Regexp) MatchReader(r io.RuneReader) bool
func (re *Regexp) MatchString(s string) bool

regexp.Regexp 的 查找方法:

// 从指定 文本/字符串 中查找匹配 re 的第一个(最左侧) 子文本/字符串, 没有找到返回零值
func (re *Regexp) Find(b []byte) []byte
func (re *Regexp) FindString(s string) string

// 从指定 文本/字符串 中查找匹配 re 的第一个(最左侧) 子文本/字符串 的首尾索引位置, 没有找到返回零值
func (re *Regexp) FindIndex(b []byte) (loc []int)
func (re *Regexp) FindStringIndex(s string) (loc []int)
func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int)


// 从指定 文本/字符串 中查找匹配 re 的所有 子文本/字符串, 
// n 表示最多查找的数量, n < 0 表示查找所有, 没有找到返回零值
func (re *Regexp) FindAll(b []byte, n int) [][]byte
func (re *Regexp) FindAllString(s string, n int) []string

// 从指定 文本/字符串 中查找匹配 re 的所有 子文本/字符串 的首尾索引位置 
// n 表示最多查找的数量, n < 0 表示查找所有, 没有找到返回零值
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int


// 从指定 文本/字符串 中查找匹配 re 的第一个(最左侧) 子文本/字符串, 
// 包括 捕获组 匹配的子项。没有找到返回零值。
func (re *Regexp) FindSubmatch(b []byte) [][]byte
func (re *Regexp) FindStringSubmatch(s string) []string

// 从指定 文本/字符串 中查找匹配 re 的第一个(最左侧) 子文本/字符串 的首尾索引位置, 
// 包括 捕获组 匹配的子项。没有找到返回零值。
func (re *Regexp) FindSubmatchIndex(b []byte) []int
func (re *Regexp) FindStringSubmatchIndex(s string) []int
func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int


// 从指定 文本/字符串 中查找匹配 re 的所有 子文本/字符串, 没有找到返回零值,
// 包括 捕获组 匹配的子项。没有找到返回零值。
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string

// 从指定 文本/字符串 中查找匹配 re 的所有 子文本/字符串 的首尾索引位置, 
// 包括 捕获组 匹配的子项。没有找到返回零值。
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int

regexp.Regexp 的 替换方法:

// 把 src 中所有匹配 re 的子文本/字符串替换为 relp, 返回副本。
// 在 repl 中, $ 被解释为在 Expand 中的符号, 因此例如 ${1} 代表编号为1的捕获组。
func (re *Regexp) ReplaceAll(src, repl []byte) []byte
func (re *Regexp) ReplaceAllString(src, repl string) string

// 把 src 中所有匹配 re 的子文本/字符串替换为 relp, 返回副本。不使用 Expand。
func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte
func (re *Regexp) ReplaceAllLiteralString(src, repl string) string

// 把 src 中所有匹配 re 的子文本/字符串 调用 relp() 函数并替换为其返回值, 返回副本。不使用 Expand。
func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte
func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string

regexp.Regexp 的 其他方法:

// 使用 re 匹配的子字符串作为分隔符拆分 字符串s, 并返回拆分后的 子字符串切片。
// 没有匹配的子字符串, 整个原字符串作为子字符串, 即返回 []string{s}。
// n > 0: 最多 n 个字符串, 最后一个子字符串将是未拆分的余数
// n == 0: 结果为 nil(零个子字符串)
// n < 0: 所有子字符串
func (re *Regexp) Split(s string, n int) []string

// 调用后未来的搜索匹配按最长匹配
func (re *Regexp) Longest()

// 返回正则表达式的原字符串表示
func (re *Regexp) String() string

// Other ...
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
func (re *Regexp) SubexpIndex(name string) int
func (re *Regexp) SubexpNames() []string
func (re *Regexp) NumSubexp() int
func (re *Regexp) LiteralPrefix() (prefix string, complete bool)

regexp.Regexp 代码示例:

package main

import (
	"fmt"
	"regexp"
)

func main() {
	// 匹配
	re := regexp.MustCompile("a[0-9]+b")
	fmt.Println(re.MatchString("123a0b456"))               // true

	// 查找
	re = regexp.MustCompile("[a-z]+")
	fmt.Println(re.FindString("12ab34cd56xyz"))            // ab
	fmt.Println(re.FindAllString("12ab34cd56xyz", -1))     // [ab cd xyz]
	fmt.Println(re.FindStringIndex("12ab34cd56xyz"))       // [2 4]

	// 替换
	re = regexp.MustCompile("[a-z]+")
	fmt.Println(re.ReplaceAllString("12ab34cd56xyz", "-"))         // 12-34-56-

	re = regexp.MustCompile("a([xy]*)b")
	fmt.Println(re.ReplaceAllString("-ab-axxb-axyb-", "A${1}B"))   // -AB-AxxB-AxyB-

	// 上面的匹配替换, 用正则表达式 "a([xy]*)b" 查找 "-ab-axxb-axyb-", 
	// 可以找出 3 个匹配子项: "ab"、"axxb" 和 "axyb"。
	// 后面的替换字符串中的 ${1} 表示捕获组:
	// 第 1 个匹配子项 "ab" 编号为 1 的捕获组为 "", 则替换的内容为 "AB";
	// 第 2 个匹配子项 "axxb" 编号为 1 的捕获组为 "xx", 则替换的内容为 "AxxB";
	// 第 3 个匹配子项 "axyb" 编号为 1 的捕获组为 "xy", 则替换的内容为 "AxyB";

	// 也可以对捕获组命名保存, 使用时使用名称代替编号
	re = regexp.MustCompile("a(?P<Hi>[xy]*)b")
	fmt.Println(re.ReplaceAllString("-ab-axxb-axyb-", "A${Hi}B"))  // -AB-AxxB-AxyB-
}

3. 正则表达式 语法

查看正则表达式语法,运行命令:go doc regexp/syntax

其他语言正则表达式文档:

字符:

x               字符x
\\              反斜杠字符 "\"
\0nn            八进制值 0nn 表示的字符 (0 <= n <= 7)
\xhh            十六禁止 0xhh 表示的字符
\uhhhh          Unicode 表示的字符
\t              制表符 ('\u0009')
\n              换行符 ('\u000A')
\r              回车符 ('\u000D')
\f              换页符 ('\u000C')
\a              报警符 ('\u0007')
\e              转义符 ('\u001B')
\x              把正则表达式的元字符 x 转义为普通字符 x

字符类:

[abc]           字符 a、b 或 c (简单类)
[^abc]          除了 a、b、c 以外的 任意字符 (否定)
[0-9]           09 范围内的字符 (范围)
[a-zA-Z]        a 到 z 或 A 到 Z 范围内的字符 (并集)
[^a-z]          除了 a 到 z 以外的任意字符 (否定+并集)

预定义字符类:

.               任意字符 (不包括换行符, 如果要无差别匹配任意字符串可以使用 [\s\S] )
\d              数字: [0-9]
\D              非数字: [^0-9]
\s              空白字符: [ \t\n\x0B\f\r]
\S              非空白字符: [^\s]
\w              单词字符: [a-zA-Z_0-9]
\W              非单词字符: [^\w]

边界匹配器:

^               行的开头
$               行的结尾
\b              单词边界
\B              非单词边界
\A              输入的开头
\G              上一个匹配的结尾
\Z              输入的结尾,仅用于最后的结束符 (如果有)
\z              输入的结尾

数量词:

x*              x, 0次 或 多次, 贪婪匹配 (最大匹配)
x+              x, 1次 或 多次, 贪婪匹配
x?              x, 0次 或 1次, 贪婪匹配
x{n,m}          x, n <= 次数 <= m, 贪婪匹配
x{n,}           x, n <= 次数, 贪婪匹配
x{n}            x, n == 次数

x*?             x, 0次 或 多次, 非贪婪 (最小匹配)
x+?             x, 1次 或 多次, 非贪婪
x??             x, 0次 或 1次, 非贪婪
x{n,m}?         x, n <= 次数 <= m, 非贪婪
x{n,}?          x, n <= 次数, 非贪婪
x{n}?           x, n == 次数

逻辑运算符:

xy              x 后面跟着 y
x|y             x 或 y

分组:

(...)           (捕获组) 分组匹配 ..., 对组内匹配到的子项进行编号保存 (从1开始编号, 整个正则表达式是一个编号为0的组)
(?P<name>...)   (捕获组) 分组匹配 ..., 对组内匹配到的子项进行编号保存的同时再使用 name 作为名称保存

(?:...)         (非捕获组) 分组匹配 ..., 不保存组内匹配到的子项

(?flags)        (非捕获组) 设置当前组内的标记, 当前组内(此标记括号外所在组)有效
(?flags:...)    (非捕获组) 设置 ... 期间的标记, ... 子表达式内有效

                flags 标记是 'a', 'i', 'L', 'm', 's', 'u', 'x' 中的 0 或 多个:
                'a'         只匹配ASCII字符
                'i'         忽略大小写
                'L'         语言依赖
                'm'         多行模式
                's'         点 dot 匹配全部字符
                'u'         Unicode 匹配
                'x'         冗长模式

(?flags1-flags2:...)    还可以对已设置的标记去除, 
                        表示当前组内设置标记 flags1 并去除标记 flags2
                
                捕获组 与 非捕获组: 
                
                    1. 捕获组会把分组匹配的组内子项结果保存起来, 
                       调用诸如 FindSubmatch() 的包含 Submatch 关键字的查找方法会把捕获的组内子项结果一起返回。
                    
                    2. 非捕获组则也是分组匹配, 但结果不保存。
                    
                    3. 捕获组可以通过从左到右计算其开括号来编号, 例如表达式 ((A)(B(C))) 中, 存在 4 个捕获组:

                        1       ((A)(B(C)))
                        2       (A)
                        3       (B(C))
                        4       (C)
                    
                    4. 整个正则表达式是一个编号为 0 的捕获组。

                

前瞻后视 (非捕获组):

re1(?=re2)      前瞻断言 (肯定 后面), 匹配 re1, 但后面必须是 re2 (结果子项不包含 re2)
re1(?!re2)      前瞻断言 (否定 后面), 匹配 re1, 但后面必须不是 re2 (结果子项不包含 re2)

(?<=re2)re1     后视断言 (肯定 前面), 匹配 re1, 但前面必须是 re2 (结果子项不包含 re2)
(?<!re2)re1     后视断言 (否定 前面), 匹配 re1, 但前面必须不是 re2 (结果子项不包含 re2)

                re1 和 re2 表示正则表达式中的子表达式。
                括号内的断言子表达式只做断言 (不消费原字符串), 前面/后面的子表达式可以继续匹配。

                前瞻: 站在后面才能向前看, 断言的是后面, 即 前断后。
                后视: 站在前面才能向后看, 断言的是前面, 即 后断前。
                
                PS: Go 内置的 Regexp 目前好像还不支持前瞻后视。

代码示例:

package main

import (
	"fmt"
	"regexp"
)

func main() {
	// 3 ~ 6个数字 (贪婪匹配, 最大匹配)
	re := regexp.MustCompile(`\d{3,6}`)
	fmt.Println(re.FindAllString("ab12345678cd", -1))      // [123456]

	// 3 ~ 6个数字 (非贪婪匹配, 最小匹配)
	re = regexp.MustCompile(`\d{3,6}?`)
	fmt.Println(re.FindAllString("ab12345678cd", -1))      // [123 456]

	// 3 个以上 纯数字 或 纯字母 (逻辑或)
	re = regexp.MustCompile(`\d{3,}|[a-zA-Z]{3,}`)
	fmt.Println(re.FindAllString("abc12de345", -1))        // [abc 345]

	// 连续三个除了0-5以外的任意字符 (否定)
	re = regexp.MustCompile(`[^0-5]{3}`)
	fmt.Println(re.FindAllString("123456789abc", -1))      // [678 9ab]

	// 忽略大小写 (全局/整个表达式捕获组内)
	re = regexp.MustCompile(`(?i)abC`)
	fmt.Println(re.FindAllString("12abC34ABc56", -1))      // [abC ABc]

	// 忽略大小写 (仅捕获组内)
	re = regexp.MustCompile(`a(?i:b)C`)
	fmt.Println(re.FindAllString("12abC34ABc56", -1))      // [abC]

	// 捕获组
	re = regexp.MustCompile(`a([0-9]*)b`)
	fmt.Println(re.FindAllString("12ab345a67b890", -1))         // [ab a67b]
	fmt.Println(re.FindAllStringSubmatch("12ab345a67b890", -1)) // [[ab ] [a67b 67]]    // 包含了捕获组

	// 非捕获组
	re = regexp.MustCompile(`a(?:[0-9]*)b`)
	fmt.Println(re.FindAllStringSubmatch("12ab345a67b890", -1)) // [[ab] [a67b]]
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谢TS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值