ZetCode

Golang Regexp.FindAllSubmatch

最后修改于 2025 年 4 月 20 日

本教程将介绍如何在 Go 中使用 `Regexp.FindAllSubmatch` 方法。我们将涵盖子匹配项提取基础知识,并提供实际示例。

子匹配项 (submatch) 是正则表达式中由括号括起来的子表达式匹配的字符串的一部分。子匹配项允许提取匹配项的特定部分。

`Regexp.FindAllSubmatch` 方法在字节切片中返回正则表达式的所有连续匹配项,包括所有子匹配项。每个匹配项都是一个字节切片组成的切片。

基本的 FindAllSubmatch 示例

`FindAllSubmatch` 最简单的用法是从字符串中提取所有匹配项和子匹配项。在这里,我们查找文本中的日期组件。

basic_submatch.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    text := "Dates: 2025-04-20, 2025-05-21, 2025-06-22"
    re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)

    matches := re.FindAllSubmatch([]byte(text), -1)
    for _, match := range matches {
        fmt.Printf("Full match: %s\n", match[0])
        fmt.Printf("Year: %s, Month: %s, Day: %s\n", 
            match[1], match[2], match[3])
    }
}

该模式在单独的分组中捕获年份、月份和日期。`FindAllSubmatch` 返回所有匹配项及其子匹配项。每个匹配项都是一个切片,其中索引 0 是完整匹配项。

提取键值对

`FindAllSubmatch` 可以从结构化文本中解析键值对。此示例提取配置值。

key_value_pairs.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    config := `
    host = localhost
    port = 8080
    timeout = 30
    debug = true
    `
    re := regexp.MustCompile(`(\w+)\s*=\s*(\S+)`)

    matches := re.FindAllSubmatch([]byte(config), -1)
    for _, match := range matches {
        fmt.Printf("Key: %-10s Value: %s\n", match[1], match[2])
    }
}

该模式将单词字符作为键,将非空白字符作为值进行匹配。子匹配项分别捕获键和值,以便于处理。

查找 HTML 属性

我们可以使用 `FindAllSubmatch` 从 HTML 标签中提取属性。此示例查找锚点标签中的所有 href 属性。

html_attributes.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    html := `
    <a href="https://example.com">Example</a>
    <a href="/about">About</a>
    <a class="nav" href="/contact">Contact</a>
    `
    re := regexp.MustCompile(`<a\b[^>]*\shref="([^"]*)"`)

    matches := re.FindAllSubmatch([]byte(html), -1)
    for _, match := range matches {
        fmt.Printf("Link: %s\n", match[1])
    }
}

该模式匹配锚点标签并捕获 href 属性值。请注意,对于复杂的 HTML 解析,通常使用专用解析器更好。

解析日志条目

日志文件通常具有可以通过正则表达式解析的统一格式。此示例从 Apache 日志条目中提取组件。

log_parsing.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    log := `127.0.0.1 - frank [10/Oct/2025:13:55:36 -0700] "GET /apache_pb.gif HTTP/1.0" 200 2326`
    pattern := `^(\S+) (\S+) (\S+) \[([^\]]+)\] "(\S+) (\S+) (\S+)" (\d+) (\d+)$`
    re := regexp.MustCompile(pattern)

    matches := re.FindAllSubmatch([]byte(log), -1)
    if len(matches) > 0 {
        match := matches[0]
        fmt.Printf("IP: %s\n", match[1])
        fmt.Printf("User: %s\n", match[3])
        fmt.Printf("Date: %s\n", match[4])
        fmt.Printf("Method: %s\n", match[5])
        fmt.Printf("Path: %s\n", match[6])
        fmt.Printf("Status: %s\n", match[8])
    }
}

该模式匹配 Common Log Format 条目的所有组件。每个组件都在单独的子匹配项中捕获,以便于访问。

提取多个电话号码

`FindAllSubmatch` 可以查找文本中模式的所有出现。此示例提取不同格式的电话号码。

phone_numbers.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    text := `
    Contact us at 555-123-4567 or (555) 987-6543.
    International: +1 555 789 0123.
    `
    re := regexp.MustCompile(`(\+?\d{1,3}[-. ]?)?\(?(\d{3})\)?[-. ]?(\d{3})[-. ]?(\d{4})`)

    matches := re.FindAllSubmatch([]byte(text), -1)
    for i, match := range matches {
        fmt.Printf("Phone #%d: %s%s%s%s\n", 
            i+1, match[1], match[2], match[3], match[4])
    }
}

该模式匹配各种电话号码格式。每个组件(国家代码、区号等)分别捕获,以实现灵活处理。

查找嵌套子匹配项

复杂的模式可能包含嵌套的子匹配项。此示例显示了如何访问不同级别的子匹配项。

nested_submatches.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    text := "Coordinates: (40.7128, -74.0060), (34.0522, -118.2437)"
    re := regexp.MustCompile(`\((\d+\.\d+),\s*(-?\d+\.\d+)\)`)

    matches := re.FindAllSubmatch([]byte(text), -1)
    for i, match := range matches {
        fmt.Printf("Point %d: Lat=%s, Lon=%s\n", 
            i+1, match[1], match[2])
    }
}

该模式匹配括号中的纬度-经度对。每个坐标组件都在其自己的子匹配项中捕获,以便单独访问。

限制匹配数量

`FindAllSubmatch` 的第二个参数控制返回的匹配项数量。此示例显示了限制匹配项数量。

limit_matches.go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    text := "apple banana apple cherry apple date apple"
    re := regexp.MustCompile(`(apple)`)

    // Find first 2 matches
    matches := re.FindAllSubmatch([]byte(text), 2)
    fmt.Printf("Found %d matches:\n", len(matches))
    for _, match := range matches {
        fmt.Println(string(match[0]))
    }

    // Find all matches
    matches = re.FindAllSubmatch([]byte(text), -1)
    fmt.Printf("\nFound %d total matches\n", len(matches))
}

设置一个正整数会限制返回的匹配项数量。使用 -1 会返回输入字符串中的所有匹配项。

来源

Go regexp 包文档

本教程通过从各种文本模式中提取子匹配项的实际示例,介绍了 Go 中的 `Regexp.FindAllSubmatch` 方法。

作者

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

列出所有 Go 教程