Golang Regexp.SubexpIndex
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.
Basic SubexpIndex Example
The simplest use of SubexpIndex
gets the index of a named group.
Here we extract date components using named groups.
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.
Checking for Group Existence
SubexpIndex
returns -1 for non-existent groups. This example shows
how to safely check for group existence.
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.
Using with FindAll Submatches
SubexpIndex
works with FindAllStringSubmatch
for
processing multiple matches. Here we parse multiple log entries.
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.
Dynamic Group Access
SubexpIndex
enables dynamic group access based on runtime values.
This example shows processing different field names.
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.
Combining with SubexpNames
SubexpIndex
can be combined with SubexpNames
to
process all named groups. Here we print all group names and values.
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.
Error Handling with Invalid Patterns
When using Compile
(not MustCompile
), we need to
handle potential errors in named group patterns.
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.
Complex Pattern with Nested Groups
SubexpIndex
works with complex patterns containing nested groups.
This example parses a configuration line.
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.
Source
Go regexp package documentation
This tutorial covered the Regexp.SubexpIndex
method in Go with
practical examples of named capture group usage in regular expressions.
Author
List all Go tutorials.