如何在Go中使用反向引用匹配正则表达式?

15

我需要在我的Go代码中匹配使用反向引用(例如\1)的正则表达式。

这并不容易,因为在Go中,官方regexp包使用RE2引擎,它选择不支持反向引用(以及一些其他不太常见的功能),以便可以保证线性时间执行,从而避免正则表达式拒绝服务攻击。启用RE2的反向引用支持不是一个选项。

在我的代码中,没有受到攻击者恶意利用的风险,我需要反向引用。

我该怎么办?


2
你确定你需要这个花哨的正则表达式吗?你想解决什么问题? - fabrizioM
5个回答

13

在这里回答自己的问题,我使用了golang-pkg-pcre来解决这个问题。它使用libpcre++和支持反向引用的perl正则表达式。它的API并不相同


1
这很棒,因为我需要与现有系统具有逐字逐句的兼容性。 - Riking
你能否提供一个例子?我找到了那个包,但无法让反向引用起作用,后来在其跟踪器上发现了一个问题,表明其他人也无法使用。 - ESR
@ESR,我找到了我2014年的旧代码,它仍然可以运行。即使是你的示例代码也能正常运行。https://gist.github.com/eldritchconundrum/790680bec2a4ce34b6d7a346d239e3a5 - Eldritch Conundrum
@esr 哦,等等,你是指替换字符串中的反向引用吗?不,它们似乎不起作用。我只需要在模式字符串中使用反向引用,所以我没有注意到。 - Eldritch Conundrum
对于那些好奇的人,@ESR所指的错误是: https://github.com/glenn-brown/golang-pkg-pcre/issues/8 - brent saner

9
正则表达式非常适合处理正则语法,但如果你的语法不是正则的(即需要反向引用等内容),那么你应该切换到更好的工具。有许多可用于解析上下文无关语法的好工具,包括 yacc,它默认与 Go 发行版一起提供。或者,您也可以编写自己的解析器。例如,递归下降解析器 可以很容易地手动编写。
我认为在脚本语言(如 Perl、Python、Ruby 等)中,正则表达式被过度使用,因为它们的 C/ASM 实现通常比这些语言本身更优化,但 Go 并不是这样的语言。正则表达式通常相当缓慢,并且经常根本不适合解决问题。

正则表达式即使对于非正则语法也有合法的用途。我同意使用正则表达式进行解析通常是一个糟糕的选择,但并不是所有的正则表达式都用于解析,例如在您喜欢的文本编辑器中进行正则表达式搜索。在我的情况下,我没有使用自定义解析器的必要,我真正需要的是正则表达式,这是规范。 - Eldritch Conundrum
6
正则表达式无法匹配非正则语法。 - thwd
4
@tomwilde - 当然会。现代正则表达式工具中的引擎已经有几十年没有被“REGULAR”了。 - ridgerunner
4
这就是为什么它们被称为“扩展”的正则表达式。 - fuz
不要由了解相关知识的人以外的任何人使用@fuz。这会导致与POSIX ERE混淆(具有讽刺意味的是,它并不是非常“扩展”,甚至不支持反向引用)。 - hobbs

3

当我遇到同样的问题时,我使用了一个两步正则表达式匹配来解决它。原始代码是:

if m := match(pkgname, `^(.*)\$\{DISTNAME:S(.)(\\^?)([^:]*)(\\$?)\2([^:]*)\2(g?)\}(.*)$`); m != nil {
    before, _, left, from, right, to, mod, after := m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8]
    // ...
}

这段代码的作用是解析形如${DISTNAME:S|from|to|g}的字符串,它本身是一种使用熟悉的替换语法S|replace|with|的小型模式语言。

这个两阶段的代码看起来像这样:

if m, before, sep, subst, after := match4(pkgname, `^(.*)\$\{DISTNAME:S(.)([^\\}:]+)\}(.*)$`); m {
    qsep := regexp.QuoteMeta(sep)
    if m, left, from, right, to, mod := match5(subst, `^(\^?)([^:]*)(\$?)`+qsep+`([^:]*)`+qsep+`(g?)$`); m {
        // ...
    }
}

matchmatch4match5是我对regexp包的封装,它们会缓存已编译的正则表达式,以便至少不会浪费编译时间。


1

我认为这是一个老问题了,但我从以上答案中没有找到简单的解决方案。

此外,“golang-pkg-pcre”在使用M1的macOS上无法使用。

因此,我想提出我的想法。

例如,将<u>或<I>替换为<b>,并用</u>或</I>替换为</b>。搜索不区分大小写。

让我比较一下如何在Python和Go中实现

在Python中,只需以下简单代码即可:

import re
content = "<u>test1</u> <i>test2</i>\n<U>test3</U> <I>test4</I>"
content = re.sub(r"<(u|i)>([^<>]+?)</\1>", r"<b>\2</b>", content, flags=re.IGNORECASE)
print(content)

在Go中,我是这样做的:
package main

import (
    "fmt"
    "regexp"
)

func main() {
    content := "<u>test1</u> <i>test2</i>\n<U>test3</U> <I>test4</I>"
    content = changeUITagToBTag(content)
    fmt.Println(content)
}

// change <u> or <i> to <b> and </u> or </i> to </b>
// case-insensitive search
func changeUITagToBTag(content string) string {
    pattern := `<(u|i)>([^<>]+?)</(u|i)>`
    compiledPattern := regexp.MustCompile(fmt.Sprintf(`(?%v)%v`, "i", pattern))
    content = compiledPattern.ReplaceAllStringFunc(content, func(text string) string {
        allSubStrings := compiledPattern.FindAllStringSubmatch(text, -1)
        if allSubStrings[0][1] == allSubStrings[0][3] {
            return fmt.Sprintf(`<b>%s</b>`, allSubStrings[0][2])
        }
        return text
    })
    return content
}

1

正则表达式包的函数 FindSubmatchIndexExpand 可以通过反向引用来捕获内容。虽然不是很方便,但仍然是可能的。示例

package main

import (
    "fmt"
    "regexp"
)

func main() {
    content := []byte(`
    # comment line
    option1: value1
    option2: value2

    # another comment line
    option3: value3
`)

    pattern := regexp.MustCompile(`(?m)(?P<key>\w+):\s+(?P<value>\w+)$`)

    template := []byte("$key=$value\n")
    result := []byte{}
    for _, submatches := range pattern.FindAllSubmatchIndex(content, -1) {
        result = pattern.Expand(result, template, content, submatches)
    }
    fmt.Println(string(result))
}

输出

option1=value1
option2=value2
option3=value3


这个问题关于其他事情,但我正在寻找这个答案。 - Ярослав Рахматуллин

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接