)
文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载正则表达式Regular Expression是 Go Web 开发中处理文本校验与提取的核心工具在服务端验证用户输入是否合法、从抓取的 HTML 页面中过滤标签、抽取结构化信息都需要依靠regexp标准库。本篇以本仓库《build-web-application-with-golang》第 7.3 节为基础系统讲解 Go 语言regexp包的匹配判断、编译解析、查找、替换与 Expand 展开等全部核心 API并结合仓库中的表单验证源码给出可直接落地的实战示例。读完本文你将能熟练使用 Go 正则完成「判断是否匹配」与「提取/过滤内容」两大类任务。1. 正则与 Go 的 regexp 包正则表达式是一种进行模式匹配和文本操作的复杂而强大的工具。虽然正则表达式比纯粹的文字匹配效率低但是它却更灵活——按照语法规则随时构造出的匹配模式就能从原始文本中筛选出几乎任何你想要的字符组合。在 Web 开发中当你需要从文本数据源中取数据时只需按照语法规则构造出正确的模式字符串就能从原始数据源提取出有意义的文本信息。Go 语言通过regexp标准包为正则表达式提供了官方支持。如果你已经使用过其他语言提供的正则功能那么对 Go 版本不会太陌生但它们之间也有一些小的差异Go 实现的是 RE2 标准除\C外RE2 采用线性时间算法避免了回溯导致的最坏情况灾难性性能问题这也是 Go 正则在处理不可信输入时更安全的原因。关于 RE2 的详细语法描述可参考官方 Syntax 文档。1.1 strings 包能解决的优先用 strings其实字符串处理我们还可以使用strings包进行搜索Contains、Index、替换Replace和解析Split、Join等操作但是这些都属于简单的字符串操作——它们的搜索区分大小写而且匹配的是固定字符串。如果我们需要匹配可变内容如任意数字、任意长度的字符段strings包就无法实现了。当然如果strings包能解决你的问题就尽量使用它它们足够简单而且性能和可读性都会优于正则。1.2 与表单验证小节的衔接如果你还记得在前面表单验证小节中我们已经接触过正则处理——在那里利用它验证输入信息是否满足预设条件如年龄是否为数字、姓名是否全为中文、邮箱格式是否正确。使用中需要注意的一点是所有字符都是 UTF-8 编码的。仓库中对应源码 validator/main.go 正是这一应用的完整实现例如校验中文姓名与邮箱// 校验中文名只允许 \x{4e00}-\x{9fa5} 区间内的汉字 if m, _ : regexp.MatchString(^[\\x{4e00}-\\x{9fa5}]$, strings.Trim(str, )); !m { return errors.New(Please make sure that the chinese name only contains chinese characters.) } // 校验邮箱非空字符 非空字符 if m, err : regexp.MatchString(^[^][^]$, str); !m { return errors.New(Please enter a valid email address.) }可以看到regexp.MatchString正是表单服务端校验中最常用、最直接的入口。接下来让我们更深入地学习 Go 语言regexp包的相关知识。2. 通过正则判断是否匹配regexp包中含有三个函数用来判断是否匹配如果匹配则返回 true否则返回 falsefunc Match(pattern string, b []byte) (matched bool, error error) func MatchReader(pattern string, r io.RuneReader) (matched bool, error error) func MatchString(pattern string, s string) (matched bool, error error)上面的三个函数实现了同一个功能判断pattern是否与输入源匹配匹配就返回 true如果解析正则出错则返回 error。三个函数的输入源分别是byte slice、RuneReader和string。2.1 实战判断 IP 地址如果要验证一个输入是不是 IP 地址该如何判断请看如下实现func IsIP(ip string) (b bool) { if m, _ : regexp.MatchString(^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$, ip); !m { return false } return true }可以看到regexp的 pattern 和我们平常使用的正则一模一样^与$锚定整个字符串[0-9]{1,3}匹配 1 到 3 位数字\\.转义匹配字面点号。2.2 实战判断用户输入是否为数字再来看一个例子当用户输入一个字符串我们想知道它是不是一次合法的数字输入func main() { if len(os.Args) 1 { fmt.Println(Usage: regexp [string]) os.Exit(1) } else if m, _ : regexp.MatchString(^[0-9]$, os.Args[1]); m { fmt.Println(數字) } else { fmt.Println(不是數字) } }该程序从命令行参数os.Args[1]取输入用^[0-9]$判断是否全部由数字组成。在上面的两个小例子中我们采用了Match(String)来判断一些字符串是否符合我们的描述需求它们使用起来非常方便。在仓库的表单验证场景中判断年龄是否为数字也采用了完全相同的模式见 validator/main.go 中的checkAge配合strconv.Atoi做类型转换与范围判断。3. 通过正则取得内容Match模式只能用于对字符串的判定而无法截取字符串的一部分、过滤字符串、或者提取出符合条件的一批字符串。如果想要满足这些需求那就需要使用正则表达式的复杂模式。我们经常需要一些爬虫程序下面就以爬虫为例来说明如何使用正则来过滤或截取抓取到的数据package main import ( fmt io/ioutil net/http regexp strings ) func main() { resp, err : http.Get(http://www.baidu.com) if err ! nil { fmt.Println(http get error.) } defer resp.Body.Close() body, err : ioutil.ReadAll(resp.Body) if err ! nil { fmt.Println(http read error) return } src : string(body) //將 HTML 標籤全轉換成小寫 re, _ : regexp.Compile(\\[\\S\\s]?\\) src re.ReplaceAllStringFunc(src, strings.ToLower) //去除 STYLE re, _ regexp.Compile(\\style[\\S\\s]?\\/style\\) src re.ReplaceAllString(src, ) //去除 SCRIPT re, _ regexp.Compile(\\script[\\S\\s]?\\/script\\) src re.ReplaceAllString(src, ) //去除所有尖括號內的 HTML 程式碼並換成換行符 re, _ regexp.Compile(\\[\\S\\s]?\\) src re.ReplaceAllString(src, \n) //去除連續的換行符 re, _ regexp.Compile(\\s{2,}) src re.ReplaceAllString(src, \n) fmt.Println(strings.TrimSpace(src)) }这个例子的处理管线清晰展示了正则的典型用法http.Get抓取页面 →ioutil.ReadAll读取响应体\\[\\S\\s]?\\匹配所有尖括号 HTML 标签用ReplaceAllStringFunc(src, strings.ToLower)将标签统一转为小写便于后续精确匹配用\\style[\\S\\s]?\\/style\\与\\script[\\S\\s]?\\/script\\分别移除style、script块再次用\\[\\S\\s]?\\把剩余标签替换为换行符用\\s{2,}将连续的空白字符压缩为单个换行符最后strings.TrimSpace去掉首尾空白输出纯净的页面文本。注意其中的[\\S\\s]?是非贪婪匹配任意字符包括换行的惯用写法。从这个例子可以看出使用复杂正则的第一步是Compile它会解析正则表达式是否合法如果正确就返回一个*Regexp然后就可以利用返回的Regexp在任意字符串上执行需要的操作。4. 解析正则表达式的方法解析正则表达式有如下几个方法func Compile(expr string) (*Regexp, error) func CompilePOSIX(expr string) (*Regexp, error) func MustCompile(str string) *Regexp func MustCompilePOSIX(str string) *Regexp这四者的区别要点如下函数语法约束搜索策略错误处理Compile通用 RE2 语法最左方式leftmost-first返回errorCompilePOSIX必须使用 POSIX 语法最左最长方式leftmost-longest返回errorMustCompile通用 RE2 语法最左方式解析失败直接panicMustCompilePOSIX必须使用 POSIX 语法最左最长方式解析失败直接panicCompilePOSIX 与 Compile 的不同点在于POSIX 必须使用 POSIX 语法它使用最左最长方式搜索而 Compile 只采用最左方式搜索。例如对[a-z]{2,4}这样一个正则表达式应用于aa09aaa88aaaa这个文本串时CompilePOSIX返回aaaa而Compile返回的是aa。前缀带 Must 的函数表示在解析正则语法的时候如果匹配模式串不满足正确的语法则直接 panic而不带 Must 的版本则只返回错误由调用方决定如何处理。在编写爬虫、日志解析等「模式是硬编码常量」的场景中MustCompile更常用——它可以在程序启动时立即暴露正则有误的问题如文档的 Expand 示例中所用的regexp.MustCompile(...)。5. Regexp 的查找方法在了解如何创建一个Regexp之后我们再来看一下这个 struct 提供了哪些方法来辅助我们操作字符串。首先来看这些用于搜索的函数func (re *Regexp) Find(b []byte) []byte func (re *Regexp) FindAll(b []byte, n int) [][]byte func (re *Regexp) FindAllIndex(b []byte, n int) [][]int func (re *Regexp) FindAllString(s string, n int) []string func (re *Regexp) FindAllStringIndex(s string, n int) [][]int func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int func (re *Regexp) FindIndex(b []byte) (loc []int) func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int) func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int func (re *Regexp) FindString(s string) string func (re *Regexp) FindStringIndex(s string) (loc []int) func (re *Regexp) FindStringSubmatch(s string) []string func (re *Regexp) FindStringSubmatchIndex(s string) []int func (re *Regexp) FindSubmatch(b []byte) [][]byte func (re *Regexp) FindSubmatchIndex(b []byte) []int上面这 18 个函数根据输入源byte slice、string 和 io.RuneReader不同还可以简化成如下几个——其余只是输入源不一样功能基本是一样的func (re *Regexp) Find(b []byte) []byte func (re *Regexp) FindAll(b []byte, n int) [][]byte func (re *Regexp) FindAllIndex(b []byte, n int) [][]int func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int func (re *Regexp) FindIndex(b []byte) (loc []int) func (re *Regexp) FindSubmatch(b []byte) [][]byte func (re *Regexp) FindSubmatchIndex(b []byte) []int使用时的关键语义Find返回第一个匹配到的内容byte sliceFindAll返回全部匹配内容第二个参数n控制返回数量n 小于 0 表示返回全部否则返回指定长度FindIndex/FindAllIndex返回匹配内容的起始与结束位置[]int长度为 2 或 2nFindSubmatch/FindAllSubmatch返回包含分组捕获的匹配结果[][]byte的第一个元素是整体匹配后续元素依次是第 1、第 2 个()分组的内容FindSubmatchIndex/FindAllSubmatchIndex返回分组的起止位置索引。5.1 完整示例Find 系列函数对于这些函数的使用我们来看下面这个例子package main import ( fmt regexp ) func main() { a : I am learning Go language re, _ : regexp.Compile([a-z]{2,4}) //查詢符合正則的第一個 one : re.Find([]byte(a)) fmt.Println(Find:, string(one)) //查詢符合正則的所有 slice,n 小於 0 表示回傳全部符合的字串不然就是回傳指定的長度 all : re.FindAll([]byte(a), -1) fmt.Println(FindAll, all) //查詢符合條件的 index 位置開始位置和結束位置 index : re.FindIndex([]byte(a)) fmt.Println(FindIndex, index) //查詢符合條件的所有的 index 位置n 同上 allindex : re.FindAllIndex([]byte(a), -1) fmt.Println(FindAllIndex, allindex) re2, _ : regexp.Compile(am(.*)lang(.*)) //查詢 Submatch回傳陣列第一個元素是匹配的全部元素第二個元素是第一個()裡面的第三個是第二個()裡面的 //下面的輸出第一個元素是am learning Go language //第二個元素是 learning Go 注意包含空格的輸出 //第三個元素是uage submatch : re2.FindSubmatch([]byte(a)) fmt.Println(FindSubmatch, submatch) for _, v : range submatch { fmt.Println(string(v)) } //定義和上面的 FindIndex 一樣 submatchindex : re2.FindSubmatchIndex([]byte(a)) fmt.Println(submatchindex) //FindAllSubmatch查詢所有符合條件的子匹配 submatchall : re2.FindAllSubmatch([]byte(a), -1) fmt.Println(submatchall) //FindAllSubmatchIndex查詢所有字匹配的 index submatchallindex : re2.FindAllSubmatchIndex([]byte(a), -1) fmt.Println(submatchallindex) }运行后可以直观看到Find得到首个匹配amFindAll得到所有[a-z]{2,4}片段am、learning、Go、language等FindSubmatch中re2的正则am(.*)lang(.*)会返回三个元素——整体am learning Go language、第一个分组 learning Go 注意包含空格和第二个分组uage。这正是从文本中按分组抽取字段的核心手段。6. Regexp 的匹配方法前面介绍过匹配函数Regexp也定义了三个方法它们和同名的包级函数功能一模一样——其实包级函数Match、MatchReader、MatchString内部就是调用这三个方法来实现的func (re *Regexp) Match(b []byte) bool func (re *Regexp) MatchReader(r io.RuneReader) bool func (re *Regexp) MatchString(s string) bool也就是说当你已经通过Compile持有*Regexp时无需再传入 pattern 字符串直接调用re.MatchString(s)即可完成判断同时避免了对同一模式反复编译的开销。7. 替换函数接下来了解替换函数是如何操作的func (re *Regexp) ReplaceAll(src, repl []byte) []byte func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte func (re *Regexp) ReplaceAllLiteralString(src, repl string) string func (re *Regexp) ReplaceAllString(src, repl string) string func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string使用要点ReplaceAll/ReplaceAllString将所有匹配到的内容替换为repl且repl中支持$1、${name}形式的反向引用ReplaceAllFunc/ReplaceAllStringFunc第二个参数是回调函数对每个匹配到的内容调用该函数用返回值替换前述爬虫示例中用strings.ToLower做函数式替换即属此类ReplaceAllLiteral/ReplaceAllLiteralString字面替换repl中的$不会做反向引用展开按原样输出。这些替换函数我们在上面的抓网页例子中有详细应用示范去除style、script、压缩连续空白等。8. Expand基于模板与分组展开文本接下来看一下 Expand 的说明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) []byteExpand的用途是在已经通过FindSubmatchIndex拿到分组位置信息的基础上按照template模板把匹配到的分组内容展开追加到dst中。它通常与FindAllSubmatchIndex配合使用用于批量重构文本。请看下面的例子func main() { src : []byte( call hello alice hello bob call hello eve ) pat : regexp.MustCompile((?m)(call)\s(?Pcmd\w)\s(?Parg.)\s*$) res : []byte{} for _, s : range pat.FindAllSubmatchIndex(src, -1) { res pat.Expand(res, []byte($cmd($arg)\n), src, s) } fmt.Println(string(res)) }关键点解读正则(?m)(call)\s(?Pcmd\w)\s(?Parg.)\s*$中(?m)开启多行模式$匹配行尾(?Pcmd...)、(?Parg...)是命名分组FindAllSubmatchIndex(src, -1)一次性找出所有匹配行的分组位置[]int对每个匹配位置pat.Expand(res, []byte($cmd($arg)\n), src, s)按模板$cmd($arg)展开——$cmd、$arg引用命名分组的内容展开结果追加进res最终输出为hello(alice) hello(eve)注意hello bob一行因不满足(call)前缀条件未被匹配这正是用正则做「条件化文本重构」的典型场景。9. 小结至此我们已完整介绍 Go 语言的regexp包。回顾全篇核心能力可归纳为四类判断匹配regexp.Match/MatchReader/MatchString以及*Regexp上的同名方法适合表单校验、输入合法性检查编译解析Compile/CompilePOSIX/MustCompile/MustCompilePOSIX注意 POSIX 语法的最左最长与普通模式最左搜索的差异以及 Must 前缀的 panic 行为查找提取Find、FindAll、FindIndex、FindSubmatch及其 Index 变体配合分组捕获从文本中抽取结构化字段替换与展开ReplaceAll系列含函数式与字面替换用于清洗/改写文本Expand/ExpandString基于命名分组与模板批量重构文本。在实际的 Go Web 开发中正则的典型落地路径是表单数据 →regexp.MatchString校验如仓库 validator/main.go 所示爬虫抓取 →CompileReplaceAll清洗 HTML日志与文本解析 →FindAllSubmatchExpand抽取字段。掌握了regexp包你就拥有了对任意文本进行模式匹配与内容操纵的完整能力。本文为《build-web-application-with-golang》第 7.3 节的深度展开相关章节可继续阅读目录、上一节 JSON 处理、下一节 模板处理以及本节正则的前置应用场景表单验证。赞分享文档教程【免费下载链接】build-web-application-with-golangA golang ebook intro how to build a web with golang项目地址https://gitcode.com/gh_mirrors/bu/build-web-application-with-golang点击查看免费下载相关推荐用 Go regexp 包实现文本匹配、过滤与抓取build-web-application-with-golang 第 7.3 节精讲用 Go regexp 包实现文本匹配、过滤与抓取build web application with golang 第 7.3 节精讲 本篇围绕《Build文档教程Go 正则表达式实战深入解析 Build Web Application with Golang 第 7.3 节 regexp 包Go 正则表达式实战深入解析 Build Web Application with Golang 第 7.3 节 regexp 包 本篇技术指南以开源项目 B文档教程Go Web 开发中的正则表达式实战build-web-application-with-golang 之 regexp 包匹配、查找、替换与文本清洗全解Go Web 开发中的正则表达式实战build web application with golang 之 regexp 包匹配、查找、替换与文本清洗全解 本文档教程上一篇requests-html招聘网站爬虫职位信息与薪资水平分析下一篇告别面条代码的终极指南YAPF Python自动格式化解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考