Learn how to find subexpression indexes using regular expressions in Go. Includes examples of regex subexpression handling.
last modified April 20, 2025
This tutorial explains how to use the Regexp.SubexpIndex method in Go. We’ll cover named capture groups and provide practical examples.
A named capture group in regular expressions allows assigning names to matched subpatterns. This makes patterns more readable and maintainable.
The Regexp.SubexpIndex method returns the index of the first subexpression with the given name. It returns -1 if no subexpression exists.
The simplest use of SubexpIndex gets the index of a named group. Here we extract date components using named groups.
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])
}
}
We define named groups for year, month, and day. SubexpIndex gets their positions in the match results. This makes code more readable.
SubexpIndex returns -1 for non-existent groups. This example shows how to safely check for group existence.
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")
}
}
}
We check if each group exists before accessing it. The “email” group returns -1 since it wasn’t defined in the pattern.
SubexpIndex works with FindAllStringSubmatch for processing multiple matches. Here we parse multiple log entries.
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])
}
}
}
The pattern extracts timestamp, log level, and message from each log entry. Named groups make the code clearer than numeric indices would.
SubexpIndex enables dynamic group access based on runtime values. This example shows processing different field names.
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])
}
}
}
}
We loop through desired field names and get their values dynamically. This approach works well when processing different patterns or configurations.
SubexpIndex can be combined with SubexpNames to process all named groups. Here we print all group names and values.
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 returns all group names, including empty strings for unnamed groups. We filter these out and print each named group’s value.
When using Compile (not MustCompile), we need to handle potential errors in named group patterns.
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)
}
The invalid pattern (missing closing parenthesis) would cause MustCompile to panic. Compile lets us handle the error gracefully.
SubexpIndex works with complex patterns containing nested groups. This example parses a configuration line.
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)")
}
}
}
The pattern handles both quoted and unquoted values. SubexpIndex helps access specific parts of this complex match structure clearly.
Go regexp package documentation
This tutorial covered the Regexp.SubexpIndex method in Go with practical examples of named capture group usage in regular expressions.
My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.
List all Go tutorials.