Go的正则表达式:第2部分

总览

这是关于Go中的正则表达式的分为两部分的系列教程的第二部分。 在第一部分中,我们了解了什么是正则表达式,如何在Go中表达它们,以及使用Go regexp库将文本与正则表达式模式进行匹配的基础知识。

在第二部分中,我们将集中精力充分利用regexp库,包括编译正则表达式,在文本中查找一个或多个匹配项,替换正则表达式,将子匹配项分组以及处理新行。

使用正则表达式库

regexp库为正则表达式提供了全面的支持,并且当使用同一模式匹配多个文本时,还可以编译模式以更高效地执行。 您还可以找到匹配项索引,替换匹配项和使用组。 让我们潜入。

编译您的正则表达式

有两种编译正则表达式的方法: Compile()MustCompile() 。 如果提供的模式无效,则Compile()将返回错误。 MustCompile()会惊慌。 如果您关心性能并计划多次使用同一正则表达式,则建议进行编译。 让我们将match()辅助函数更改为采用已编译的正则表达式。 请注意,由于编译后的正则表达式必须有效,因此无需检查错误。

func match(r *regexp.Regexp, text string) {
    matched := r.MatchString(text)
	if matched {
		fmt.Println("√", r.String(), ":", text)
	} else {
		fmt.Println("X", r.String(), ":", text)
	}
}

以下是多次编译和使用相同的已编译正则表达式的方法:

func main() {
    es :=  `(\bcats?\b)|(\bdogs?\b)|(\brats?\b)`
    e := regexp.MustCompile(es)
	match(e, "It's raining dogs and cats")
	match(e, "The catalog is ready. It's hotdog time!")
	match(e, "It's a dog eat dog world.")
}

Output:

√ (\bcats?\b)|(\bdogs?\b)|(\brats?\b) : 
  It's raining dogs and cats
X (\bcats?\b)|(\bdogs?\b)|(\brats?\b) : 
  The catalog is ready. It's hotdog time!
√ (\bcats?\b)|(\bdogs?\b)|(\brats?\b) : 
  It's a dog eat dog world.

寻找

Regexp对象具有很多 FindXXX()方法。 其中一些返回第一个匹配项,另一些返回所有匹配项,而另一些返回一个或多个索引。 有趣的是,所有16个函数方法的名称都与以下正则表达式匹配: Find(All)?(String)?(Submatch)?(Index)?

如果显示“全部”,则返回所有匹配项,而最左边的匹配项返回。 如果存在“字符串”,则目标文本和返回值是字符串对字节数组。 如果存在“子匹配”,则返回子匹配(组),而不是简单匹配。 如果存在“索引”,则返回目标文本中的索引与实际匹配项之间的关系。

让我们采用一种更复杂的函数来执行任务,并使用FindAllStringSubmatch()方法。 它需要一个字符串和一个数字n 。 如果n为-1,它将返回所有匹配的索引。 如果n是非负整数,则它将返回最左边的n个匹配项。 结果是一片字符串切片。

每个子匹配的结果是完整匹配,后跟捕获的组。 例如,考虑一个名称列表,其中一些名称具有“ Mr。”,“ Mrs。”或“ Dr.”之类的标题。 这是一个正则表达式,用于将标题捕获为子匹配,然后在空格后捕获名称的其余部分: \b(Mr\.|Mrs\.|Dr\.) .*

func main() {
    re := regexp.MustCompile(`\b(Mr\.|Mrs\.|Dr\.) .*`)
    fmt.Println(re.FindAllStringSubmatch("Dr. Dolittle", -1))
    fmt.Println(re.FindAllStringSubmatch(`Mrs. Doubtfire
                                          Mr. Anderson`, -1))
}

Output:

[[Dr. Dolittle Dr.]]
[[Mrs. Doubtfire Mrs.] [Mr. Anderson Mr.]]

如您在输出中看到的,首先捕获完整匹配,然后捕获标题。 对于每一行,搜索都会重置。

更换

查找匹配项很棒,但是通常您可能需要用其他替换项。 Regexp对象像往常一样具有几种ReplaceXXX()方法,用于处理字符串与字节数组以及文字替换与扩展。 在乔治·奥威尔(George Orwell) 1984年写的伟大著作中,党的口号刻在真相的白色金字塔上:

  • 战争就是和平
  • 自由就是奴隶制
  • 无知就是力量

我发现了一篇有关“自由的代价”的文章, 其中使用了一些术语。 让我们根据使用Go regexes进行的双口讲话来纠正它的一小段。 请注意,某些替换目标词使用不同的大小写。 解决方法是在正则表达式的开头添加不区分大小写的标志(i?)

由于翻译因情况而异,因此我们需要一种比文字替换更复杂的方法。 幸运的是(或设计使然),Regexp对象具有replace方法,该方法接受它用来执行实际替换的功能。 让我们定义我们的替换函数,以正确的大小写返回转换。

func replacer(s string) string {
    d := map[string]string{
		"war":       "peace",
		"WAR":       "PEACE",
		"War":       "Peace",
		"freedom":   "slavery",
		"FREEDOM":   "SLAVERY",
		"Freedom":   "Slavery",
		"ignorance": "strength",
		"IGNORANCE": "STRENGTH",
		"Ignorance": "Strength",
	}

	r, ok := d[s]
	if ok {
		return r
	} else {
		return s
	}
}

现在,我们可以执行实际的替换:

func main() {
    text := `THE PRICE OF FREEDOM: Americans at War
             Americans have gone to war to win their
             independence, expand their national
             boundaries, define their freedoms, and defend
             their interests around the globe.`

    expr := `(?i)(war|freedom|ignorance)`
    r := regexp.MustCompile(expr)

    result := r.ReplaceAllStringFunc(text, replacer)
    fmt.Println(result)
}

Output:

THE PRICE OF SLAVERY: Americans at Peace
    Americans have gone to peace to win their
    independence, expand their national
    boundaries, define their slaverys, and defend
    their interests around the globe.

产出有些不连贯,这是良好宣传的标志。

分组

前面我们已经看到了如何将分组与子匹配一起使用。 但是有时很难处理多个子匹配。 命名群组在这里可以提供很多帮助。 以下是命名子比赛组的方法,并填充字典以方便按名称进行访问:

func main() {
    e := `(?P<first>\w+) (?P<middle>.+ )?(?P<last>\w+)`
    r := regexp.MustCompile(e)
    names := r.SubexpNames()
    fullNames := []string{
        `John F. Kennedy`,
        `Michael Jordan`}
    for _, fullName := range fullNames {
        result := r.FindAllStringSubmatch(fullName, -1)
        m := map[string]string{}
        for i, n := range result[0] {
            m[names[i]] = n
        }
        fmt.Println("first name:", m["first"])
        fmt.Println("middle_name:", m["middle"])
        fmt.Println("last name:", m["last"])
        fmt.Println()
    }
}

Output:

first name: John
middle_name: F. 
last name: Kennedy

first name: Michael
middle_name: 
last name: Jordan

处理新线

如果您还记得,我说过点特殊字符可以与任何字符匹配。 好吧,我撒谎了。 默认情况下,它与换行符( \n )不匹配。 这意味着除非您使用可以添加到正则表达式开头的特殊标志(?s)明确指定匹配项,否则它们将不会跨越线。 这是带有和不带有标志的示例。

func main() {
    text := "1111\n2222"

	expr := []string{".*", "(?s).*"}
	for _, e := range expr {
		r := regexp.MustCompile(e)
		result := r.FindString(text)
		result = strings.Replace(result, "\n", `\n`, -1)
		fmt.Println(e, ":", result)
		fmt.Println()
	}
}

Output:

.* : 1111

(?s).* : 1111\n2222

另一个考虑因素是是否将^$特殊字符视为整个文本的开头和结尾(默认值)还是带有(?m)标志的每一行的开头和结尾。

结论

使用半结构化文本时,正则表达式是强大的工具。 您可以使用它们来验证文本输入,对其进行清理,对其进行转换,对其进行规范化,并且通常使用简洁的语法来处理多种变化。

Go提供了一个具有易于使用的界面的库,该界面由具有许多方法的Regexp对象组成。 试试看,但是要当心陷阱。

翻译自: https://code.tutsplus.com/tutorials/regular-expressions-with-go-part-2--cms-30406

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值