优秀的编程知识分享平台

网站首页 > 技术文章 正文

七爪源码:向现有的 Golang 工具添加正则表达式支持

nanyue 2024-11-02 12:17:52 技术文章 4 ℃

文本过滤第 2 部分:Electric Regex Boogaloo

什么是正则表达式?

正则表达式(简称 regex)是一系列指定搜索模式的字符。字符串搜索算法使用它们来识别文本的相关部分。

您可能已经熟悉通配符 - 使用 * 表示任意数量的字符(包括空字符串)。正则表达式更进一步。

它们允许您更具体地进行模式匹配。您可以使用 [^<characters to Avoid>] 搜索任何字符以外的任何字符。您可以使用 ^<phrase> 搜索以短语开头的内容。同样,您可以使用 <phrase>$ 搜索以短语结尾的内容。您可以做的还有很多,您可以在此处了解并在此处进行测试。

我们为什么关心?

日志消息通常非常适合子字符串匹配,但并非总是如此。如果我们想删除两个特定时间戳之间的日志行,比如 2022 年 8 月 1 日至 4 日,该怎么办?我们可以通过重复自己使用带有子字符串匹配的现有工具来做到这一点:

-keys="logcreated-20220801:|logcreated-20220802:|logcreated-20220803:|logcreated-20220804:

或者我们可以搜索匹配以下正则表达式的模式:

^logcreated-2022080([1-4]):

随着查询复杂性的增加,正则表达式成为自定义、可重复搜索的强大工具。


添加新功能

与上一篇文章一样,我们应该考虑用户将如何与新功能进行交互。 为简单起见,我们可以假设用户将提供一组子字符串(-keys)来匹配,或提供一个模式(-pattern),或两者兼而有之。 这将使指令和错误处理逻辑变得容易——如果既没有给出键也没有给出模式会抱怨。

要添加反向模式匹配,我们需要执行以下操作:

  • 更新自述文件和帮助说明
  • 添加新标志和错误处理
  • 编写正则表达式匹配代码
  • 更新处理循环以在正则表达式和子字符串匹配之间进行选择

为简洁起见,可以在工具仓库中查看更新的帮助和自述文件指南。


添加新标志和错误处理:

type config struct {
	inputPath string
	keys      []string
	pattern   *regexp.Regexp
	inplace   bool
}

func getUserInput() *config {
	inputPath := flag.String("file", "", "file to modify")
	keysRaw := flag.String("keys", "", "keys to search for in lines - separate multiple keys with '|'")
	patternRaw := flag.String("pattern", "", "regular expression to match against. note that lookarounds are not supported.")
	inplace := flag.Bool("inplace", false, "edit the file (don't create a copy)")
	flag.Parse()

	if *inputPath == "" {
		log.Println("file must be provided")
		helpAndExit()
	}
	if *keysRaw == "" && *patternRaw == "" {
		log.Println("keys or pattern must be provided")
		helpAndExit()
	}

	c := &config{
		inputPath: *inputPath,
		inplace:   *inplace,
	}
	if *keysRaw != "" {
		c.keys = strings.Split(*keysRaw, "|")
	}
	if *patternRaw != "" {
		re, err := regexp.Compile(*patternRaw)
		if err != nil {
			log.Printf("provided pattern %q failed to compile: %v\n", *patternRaw, err)
			helpAndExit()
		}

		c.pattern = re
	}

	return c
}

因为我们通过 getUserInput() 获取用户输入并将该配置存储在 config 中,所以我们只需要更新这些实体以添加新标志。在这里,我们只是在第 11 行定义了新标志,并在第 19 行验证它或密钥是否已传入。

对于这段代码,我们使用 regexp,另一个标准库包来处理正则表达式匹配。我们还在验证提供的模式实际上可以用作正则表达式。通过在此处执行此操作,我们可以在与用户输入相关的事情的早期并在一个地方失败。

请注意,Go 的 regexpr 不支持某些功能,例如负前瞻。做一些快速的谷歌搜索,似乎 RE2 的各种实现应该提供额外的功能,但这是另一次探索。

编写正则表达式匹配代码:

由于我们已经编译了正则表达式并将其包含在配置中,我们只需要在正则表达式上调用 MatchString(),传入字符串进行检查。这将产生一个关于是否存在匹配的布尔值。与子字符串检查类似,我们可以遍历输入文件中的所有行,对它们调用 MatchString(),并保存不匹配的行。

为了简化处理逻辑,让我们在配置中添加一个接收器:

// return true if s matches either the regular expression pattern, or one of the
// provided substrings
func (c *config) lineMatches(s string) bool {
	if c.pattern != nil && c.pattern.MatchString(s) {
		return true
	}

	return substrInLine(s, c.keys)
}

可以在配置实例上调用 lineMatches() 以确定给定的字符串是否与模式中提供的内容或关键字中提供的内容相匹配。


更新处理循环

transformInputImpl() 没有太大变化。 我们将把 *config 作为输入(而不是几个变量),我们将交换:

if !substrInLine(line, keys) {

为了:

if !cfg.lineMatches(line) {

这样做的一个很好的副作用是现在所有的线匹配逻辑都绑定在 lineMatches() 中,这使得对处理的进一步调整变得清晰和集中。


使用工具

基于我们之前的示例,我们现在可以替换:

go run main.go -file="example/input.txt" -keys="hello"

和:

go run main.go -file="example/input.txt" -pattern="hello"

这将完成同样的事情,但不是很令人兴奋或新鲜。 我们如何做这样的事情:

go run main.go -file="example/input.txt" -pattern=".*b([r]?)ig([ht]?).*"

这种模式可以通过使用 regex101 进一步解释,但本质上它匹配任何包含“big”、“brig”、“bight”或“bright”的输入行。 在examples/input.txt 中,这将只剩下hello world!

如果我们愿意,我们甚至可以提供一组关键词和一个模式!

go run main.go -file="example/input.txt" -keys="hello" -pattern=".*b([r]?)ig([ht]?).*"

不会从我们的输入文件中留下任何东西。


关注七爪网,获取更多APP/小程序/网站源码资源!

最近发表
标签列表