Featured image of post Go 正则表达式 regexp 使用$匹配行尾时在 CRLF(\r\n)上不工作

Go 正则表达式 regexp 使用$匹配行尾时在 CRLF(\r\n)上不工作

今天发现 Go 的正则表达式 regexp 库在 CRLF 上是不工作的,为了这个问题调试了好久,特此记录。

大家知道正则表达式^是用来匹配行首,$是用来匹配行尾。如果是多行全局的情况,就会分别匹配每一行。比如一个匹配 QQ 邮箱的正则^\d+@qq\.com$,用 regex101 测试的结果:

image-20241005204251769

而在 Go 里面如果要启用多行匹配一般是这样写:

1
re := regexp.MustCompile(`(?m)^\d+@qq\.com$`)

这里的(?m)就是多行匹配的意思。

全局匹配在其他语言中是一般会用 g 表示,而在 Go 中应该调用带 All 的方法,比如 FindAllString。

但是 Go 的多行全局匹配时其实对换行符有要求,不会匹配 CRLF(\r\n,Windows 中的换行)和 CR(\r,macOS 中的换行)的,只会匹配 LF(\n,Linux 中的换行)。

示例代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package main

import (
	"log/slog"
	"regexp"
)

func main() {
	str1 := "[email protected]\r\[email protected]\r\[email protected]"
	str2 := "[email protected]\[email protected]\[email protected]"
	str3 := "[email protected]\[email protected]\[email protected]"
	re := regexp.MustCompile(`(?m)^\d+@qq\.com$`)
	slog.Info("str1", "matched", re.FindAllString(str1, -1))
	slog.Info("str2", "matched", re.FindAllString(str2, -1))
	slog.Info("str2", "matched", re.FindAllString(str3, -1))
}

输出:

1
2
3
2024/10/05 20:39:16 INFO str1 matched=[[email protected]]
2024/10/05 20:39:16 INFO str2 matched="[[email protected] [email protected] [email protected]]"
2024/10/05 20:39:16 INFO str2 matched=[]

可以看到只有 str2 是正确匹配的。str1 由于最后一个邮箱后面没有\r\n,也作为字符串的结尾被匹配了。

如果是读文件的情况,可能 Windows 上的文件本来就是\r\n换行的,目前没有找到什么好的办法,只能用 strings.ReplaceAll 把\r全部都替换掉。

Rust 的正则库似乎也有这个问题:https://github.com/rust-lang/regex/issues/244

Licensed under CC BY-NC-SA 4.0