ZetCode

Golang Regexp.SubexpIndex

最后修改于 2025 年 4 月 20 日

本教程将解释如何在 Go 中使用 Regexp.SubexpIndex 方法。我们将涵盖命名捕获组并提供实际示例。

正则表达式中的 命名捕获组 允许为匹配的子模式分配名称。这使得模式更具可读性和可维护性。

Regexp.SubexpIndex 方法返回具有给定名称的第一个子表达式的索引。如果不存在子表达式,则返回 -1。

基本 SubexpIndex 示例

SubexpIndex 最简单的用法是获取命名组的索引。在这里,我们使用命名组提取日期组件。

basic_subexp.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`)
    date := "2025-04-20"

    matches := re.FindStringSubmatch(date)
    if matches != nil {
        yearIdx := re.SubexpIndex("year")
        monthIdx := re.SubexpIndex("month")
        dayIdx := re.SubexpIndex("day")
        
        fmt.Println("Year:", matches[yearIdx])
        fmt.Println("Month:", matches[monthIdx])
        fmt.Println("Day:", matches[dayIdx])
    }
}

我们为年、月、日定义了命名组。SubexpIndex 获取它们在匹配结果中的位置。这使得代码更具可读性。

检查组是否存在

SubexpIndex 对不存在的组返回 -1。此示例显示了如何安全地检查组是否存在。

check_group.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<name>\w+)\s+(?P<age>\d+)`)
    input := "John 30"

    matches := re.FindStringSubmatch(input)
    if matches != nil {
        nameIdx := re.SubexpIndex("name")
        ageIdx := re.SubexpIndex("age")
        emailIdx := re.SubexpIndex("email") // Doesn't exist

        if nameIdx != -1 {
            fmt.Println("Name:", matches[nameIdx])
        }
        if ageIdx != -1 {
            fmt.Println("Age:", matches[ageIdx])
        }
        if emailIdx != -1 {
            fmt.Println("Email:", matches[emailIdx])
        } else {
            fmt.Println("Email field not found")
        }
    }
}

在访问每个组之前,我们会检查它是否存在。“email”组返回 -1,因为它在模式中未定义。

与 FindAll Submatches 一起使用

SubexpIndexFindAllStringSubmatch 一起用于处理多个匹配。在这里,我们解析多个日志条目。

multiple_matches.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`\[(?P<time>\d{2}:\d{2}:\d{2})\] (?P<level>\w+): (?P<message>.*)`)
    logs := []string{
        "[10:23:45] ERROR: File not found",
        "[10:23:47] INFO: User logged in",
        "[10:23:49] WARNING: Disk space low",
    }

    timeIdx := re.SubexpIndex("time")
    levelIdx := re.SubexpIndex("level")
    msgIdx := re.SubexpIndex("message")

    for _, log := range logs {
        matches := re.FindStringSubmatch(log)
        if matches != nil {
            fmt.Printf("Time: %s, Level: %s, Message: %s\n",
                matches[timeIdx], matches[levelIdx], matches[msgIdx])
        }
    }
}

该模式从每个日志条目中提取时间戳、日志级别和消息。与数字索引相比,命名组使代码更清晰。

动态组访问

SubexpIndex 支持基于运行时值的动态组访问。此示例显示了处理不同的字段名。

dynamic_groups.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<first>\w+)\s+(?P<last>\w+),\s+(?P<age>\d+)`)
    input := "Smith John, 42"

    fields := []string{"first", "last", "age"}
    matches := re.FindStringSubmatch(input)

    if matches != nil {
        for _, field := range fields {
            idx := re.SubexpIndex(field)
            if idx != -1 {
                fmt.Printf("%s: %s\n", field, matches[idx])
            }
        }
    }
}

我们遍历所需的字段名并动态获取它们的值。当处理不同的模式或配置时,这种方法效果很好。

与 SubexpNames 结合使用

SubexpIndex 可以与 SubexpNames 结合使用来处理所有命名组。在这里,我们打印所有组名和值。

subexp_names.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<protocol>https?)://(?P<domain>[^/]+)/(?P<path>.*)`)
    url := "https://example.com/path/to/resource"

    matches := re.FindStringSubmatch(url)
    if matches != nil {
        for _, name := range re.SubexpNames() {
            if name != "" { // Skip unnamed groups
                idx := re.SubexpIndex(name)
                fmt.Printf("%s: %s\n", name, matches[idx])
            }
        }
    }
}

SubexpNames 返回所有组名,包括未命名组的空字符串。我们过滤掉这些,并打印每个命名组的值。

使用无效模式进行错误处理

在使用 Compile (而不是 MustCompile) 时,我们需要处理命名组模式中可能出现的错误。

error_handling.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    pattern := `(?P<name>\w+)(?P<invalid`
    re, err := regexp.Compile(pattern)
    
    if err != nil {
        fmt.Println("Error compiling regex:", err)
        return
    }

    // This would panic if we used MustCompile
    idx := re.SubexpIndex("name")
    fmt.Println("Name group index:", idx)
}

无效的模式(缺少闭括号)会导致 MustCompile 恐慌。Compile 允许我们优雅地处理错误。

带有嵌套组的复杂模式

SubexpIndex 可以与包含嵌套组的复杂模式一起使用。此示例解析了一个配置行。

nested_groups.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<key>\w+)\s*=\s*(?P<value>"(?P<quoted>[^"]*)"|(?P<unquoted>\S+))`)
    config := `title = "Welcome" timeout=30`

    keyIdx := re.SubexpIndex("key")
    valueIdx := re.SubexpIndex("value")
    quotedIdx := re.SubexpIndex("quoted")
    unquotedIdx := re.SubexpIndex("unquoted")

    matches := re.FindAllStringSubmatch(config, -1)
    for _, match := range matches {
        fmt.Println("Key:", match[keyIdx])
        fmt.Println("Value:", match[valueIdx])
        
        if match[quotedIdx] != "" {
            fmt.Println("(Quoted value)")
        } else {
            fmt.Println("(Unquoted value)")
        }
    }
}

该模式同时处理带引号和不带引号的值。SubexpIndex 有助于清晰地访问此复杂匹配结构中的特定部分。

来源

Go regexp 包文档

本教程通过实际的正则表达式命名捕获组使用示例,涵盖了 Go 中的 Regexp.SubexpIndex 方法。

作者

我的名字是 Jan Bodnar,我是一名热情的程序员,拥有丰富的编程经验。我自 2007 年以来一直在撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程