ZetCode

Golang Regexp.SubexpNames

最后修改于 2025 年 4 月 20 日

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

名为“命名捕获组”的正则表达式功能允许为捕获括号分配名称。这使得模式更具可读性。

Regexp.SubexpNames 方法返回正则表达式中命名捕获组的名称切片。第一个元素始终是整个匹配的空字符串。

基本的 SubexpNames 示例

SubexpNames 的最简单用法显示了捕获组的名称。在这里,我们检查一个带有命名组的模式。

basic_names.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`)
    names := re.SubexpNames()
    
    for i, name := range names {
        fmt.Printf("%d: %q\n", i, name)
    }
}

我们创建一个带有年、月、日命名组的日期模式。SubexpNames 按顺序返回这些名称。索引 0 始终为空。

使用命名组进行匹配

此示例显示了如何将命名组与 FindStringSubmatch 一起使用。我们可以按名称或索引访问匹配项。

named_match.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<first>\w+)\s+(?P<last>\w+)`)
    match := "John Doe"
    
    result := re.FindStringSubmatch(match)
    names := re.SubexpNames()
    
    for i, name := range names {
        if i != 0 && name != "" {
            fmt.Printf("%s: %s\n", name, result[i])
        }
    }
}

该模式捕获名字和姓氏。我们使用 SubexpNames 获取组名,并打印带有名称的匹配值。

以编程方式访问命名组

我们可以创建一个命名组的映射,以便于访问。这使得处理匹配更加方便。

named_map.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"
    
    match := re.FindStringSubmatch(url)
    names := re.SubexpNames()
    
    result := make(map[string]string)
    for i, name := range names {
        if i != 0 && name != "" {
            result[name] = match[i]
        }
    }
    
    fmt.Printf("Protocol: %s\n", result["protocol"])
    fmt.Printf("Domain: %s\n", result["domain"])
    fmt.Printf("Path: %s\n", result["path"])
}

我们使用命名组将 URL 解析为组件。该映射允许按名称而不是数字索引访问部分。

混合命名和未命名组

模式可以混合命名和未命名组。SubexpNames 为未命名的组显示空字符串。

mixed_groups.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(\w+)-(?P<middle>\w+)-(\w+)`)
    input := "first-middle-last"
    
    match := re.FindStringSubmatch(input)
    names := re.SubexpNames()
    
    for i, name := range names {
        if name == "" {
            name = fmt.Sprintf("unnamed%d", i)
        }
        fmt.Printf("%s: %s\n", name, match[i])
    }
}

该模式有两个未命名的组和一个命名的组。我们通过创建临时名称来处理未命名的组,以便显示。

验证组名

此示例在实际使用之前检查模式中是否存在特定的组名。这可以防止运行时错误。

validate_names.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<user>\w+)@(?P<domain>\w+\.\w+)`)
    email := "user@example.com"
    
    names := re.SubexpNames()
    required := []string{"user", "domain"}
    
    for _, name := range required {
        found := false
        for _, n := range names {
            if n == name {
                found = true
                break
            }
        }
        if !found {
            panic(fmt.Sprintf("missing required group: %s", name))
        }
    }
    
    match := re.FindStringSubmatch(email)
    fmt.Printf("User: %s\n", match[1])
    fmt.Printf("Domain: %s\n", match[2])
}

我们验证必需的组名是否存在于模式中。这在使用动态创建的正则表达式时非常有用。

重用组名

当组名在模式中重用时,SubexpNames 会反映这一点。每次出现都会获得相同的名称。

reused_names.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<digit>\d)(?P<digit>\d)`)
    input := "12"
    
    names := re.SubexpNames()
    match := re.FindStringSubmatch(input)
    
    for i, name := range names {
        if name != "" {
            fmt.Printf("%s: %s\n", name, match[i])
        }
    }
}

该模式重用了“digit”组名。两个数字都会被捕获,但由于组编号,只有第二个出现在结果中。

带有多个组的复杂模式

本示例说明了 SubexpNames 与包含多个命名捕获组的复杂正则表达式模式结合使用的用法。命名组允许开发人员为正则表达式中的每个组分配描述性名称,从而使模式更具可读性,提取的数据更易于解释。在处理结构化数据(例如 URL)时,标记捕获组的能力尤其有用,因为每个部分(例如方案、主机、路径)都有不同的含义,并且需要以有组织的方式进行提取。

complex_pattern.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    pattern := `^(?P<scheme>\w+)://` +
        `(?P<host>[^/:]+)` +
        `(?::(?P<port>\d+))?` +
        `(?P<path>/[^?]*)?` +
        `(?:\?(?P<query>[^#]*))?` +
        `(?:#(?P<fragment>.*))?$`
    
    re := regexp.MustCompile(pattern)
    url := "https://example.com:8080/path?query=value#frag"
    
    names := re.SubexpNames()
    match := re.FindStringSubmatch(url)
    
    for i, name := range names {
        if name != "" && i < len(match) {
            fmt.Printf("%-10s: %s\n", name, match[i])
        }
    }
}

该示例定义了一个全面的模式,用于将 URL 解析为其组件:方案、主机、端口、路径、查询和片段。该模式使用 ?P<scheme>?P<host> 等命名组来标记 URL 的每个部分,确保匹配结果有意义且具有自解释性。使用 regexp.MustCompile 编译后,正则表达式用于处理示例 URL “https://example.com:8080/path?query=value#frag”,通过应用 FindStringSubmatch 函数,该函数返回一个包含完整匹配和所有捕获组匹配的切片。

然后使用编译后的正则表达式的 SubexpNames 方法来检索模式中所有捕获组的名称。此方法返回一个切片,其中每个元素对应于匹配结果中相同索引处的捕获组。通过遍历名称和匹配结果的切片,程序会打印每个捕获组的名称及其匹配的值。SubexpNamesFindStringSubmatch 的组合演示了如何有效地使用命名组来解析复杂输入并将每个匹配项映射到其相应的标签。

来源

Go regexp 包文档

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

作者

我叫 Jan Bodnar,我是一名充满激情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程