为了账号安全,请及时绑定邮箱和手机立即绑定

Go 的正则表达式,匹配包含平衡括号的内容

Go 的正则表达式,匹配包含平衡括号的内容

Go
子衿沉夜 2023-02-28 20:20:18
所以我的用例如下:我正在解析一个 SQL 查询,试图获取函数名称和发送到该函数的各个参数。这要求我的正则表达式能够找到名称、左括号、内容和右括号。不幸的是,在测试时发现它有时过于贪婪,抓住额外的括号而其他时候它错过了结束的括号。这是我在操场上的测试代码:func getRegex(name string) string {    return fmt.Sprintf("\\$__%s\\b(?:\\((.*?\\)?)\\))?", name)}func main() {    var rawSQL = "(select min(time) from table where $__timeFilter(time))"    rgx, err := regexp.Compile(getRegex("timeFilter"))    if err != nil {        fmt.Println(err)    }    var match = rgx.FindAllStringSubmatch(rawSQL, -1)    fmt.Println(match)}举个例子https://go.dev/play/p/4FpZblia7Ks我测试的4个案例如下:(select min(time) from table where $__timeFilter(time) ) OK(select min(time) from table where $__timeFilter(time)) NOKselect * from foo where $__timeFilter(cast(sth as timestamp)) OKselect * from foo where $__timeFilter(cast(sth as timestamp) ) NOK这是一个实时正则表达式版本https://regexr.com/700oh我来自 javascript 世界,所以从未使用过递归正则表达式,看起来这可能是一种情况?
查看完整描述

2 回答

?
九州编程

TA贡献1785条经验 获得超4个赞

您的正则表达式似乎有两个主要问题,其中一个比另一个更容易处理:

  1. 正则表达式天生就不擅长处理递归匹配,例如分组左括号和右括号,因为它们没有内存。就您而言,我认为您已尝试通过将自己限制在一些特定情况下来解决此问题,但正则表达式的贪婪性质在这里对您不利。

  2. 您不匹配右括号前可能有空格的情况。

这两个问题一起导致您的正则表达式在这两种情况下失败,但也导致您的第一个案例匹配。

要解决此问题,您必须在将字符串发送到正则表达式之前对字符串进行一些预处理:

if strings.HasPrefix(rawSql, "(") {
    rawSql = rawSql[1:len(rawSql) - 1]
}

这将去掉任何外括号,如果没有内存或额外的子句,正则表达式将无法忽略这些括号。

接下来,您需要修改正则表达式以处理内部函数调用和$__timeFilter调用之间可能存在空格的情况:

func getRegex(name string) string {
    return fmt.Sprintf("\\$__%s\\b(\\((.*?\\)?)\\s*\\))?", name)
}

这样做之后,您的正则表达式应该可以工作了。您可以在此 playground 链接上找到完整示例。


查看完整回答
反对 回复 2023-02-28
?
FFIVE

TA贡献1797条经验 获得超6个赞

尽管我最终不得不走另一条路,但我还是选择了 Woody 的答案作为正确答案。附加的测试用例不包括某些场景,结果我还必须能够提取括号内的参数。所以这是我的最终解决方案,我手动解析文本,找到边界括号并提取它们之间的任何内容:


// getMacroMatches extracts macro strings with their respective arguments from the sql input given

// It manually parses the string to find the closing parenthesis of the macro (because regex has no memory)

func getMacroMatches(input string, name string) ([][]string, error) {

    macroName := fmt.Sprintf("\\$__%s\\b", name)

    matchedMacros := [][]string{}

    rgx, err := regexp.Compile(macroName)


    if err != nil {

        return nil, err

    }


    // get all matching macro instances

    matched := rgx.FindAllStringIndex(input, -1)


    if matched == nil {

        return nil, nil

    }


    for matchedIndex := 0; matchedIndex < len(matched); matchedIndex++ {

        var macroEnd = 0

        var argStart = 0

        macroStart := matched[matchedIndex][0]

        inputCopy := input[macroStart:]

        cache := make([]rune, 0)


        // find the opening and closing arguments brackets

        for idx, r := range inputCopy {

            if len(cache) == 0 && macroEnd > 0 {

                break

            }

            switch r {

            case '(':

                cache = append(cache, r)

                if argStart == 0 {

                    argStart = idx + 1

                }

            case ')':

                l := len(cache)

                if l == 0 {

                    break

                }

                cache = cache[:l-1]

                macroEnd = idx + 1

            default:

                continue

            }

        }


        // macroEnd equals to 0 means there are no parentheses, so just set it

        // to the end of the regex match

        if macroEnd == 0 {

            macroEnd = matched[matchedIndex][1] - macroStart

        }

        macroString := inputCopy[0:macroEnd]

        macroMatch := []string{macroString}


        args := ""

        // if opening parenthesis was found, extract contents as arguments

        if argStart > 0 {

            args = inputCopy[argStart : macroEnd-1]

        }

        macroMatch = append(macroMatch, args)

        matchedMacros = append(matchedMacros, macroMatch)

    }

    return matchedMacros, nil

}

游乐场链接:https://go.dev/play/p/-odWKMBLCBv


查看完整回答
反对 回复 2023-02-28
  • 2 回答
  • 0 关注
  • 147 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信