Regexp 实例


在 Go 中要使用规则表达式获取比对成功的部份、取代等任务,都得将规则表达式编译为Regexp才可以:

func Compile(expr string) (*Regexp, error)
func CompilePOSIX(expr string) (*Regexp, error)
func MustCompile(str string) *Regexp
func MustCompilePOSIX(str string) *Regexp

POSIX 结尾的函数,表示规则表达式必须符合 POSIX ERE (egrep) 语法,Must 开头的函数,表示解析错误的话会 panic。

解析成功的话,返回*Regexp,之后就是比对任务了,不用再处理错误。例如:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re, err := regexp.Compile(`\d{4}-\d{6}`)
    fmt.Println(re, err)

    matched := re.MatchString("0970-168168")
    fmt.Println(matched)
    matched = re.MatchString("Phone: 0970-168168")
    fmt.Println(matched)
}

寻找符合项目

如果想找出最左边第一个符合项目,可以使用 Find 开头的方法版本:

func (re *Regexp) Find(b []byte) []byte
func (re *Regexp) FindIndex(b []byte) (loc []int)
func (re *Regexp) FindReaderIndex(r io.RuneReader) (loc []int)
func (re *Regexp) FindReaderSubmatchIndex(r io.RuneReader) []int
func (re *Regexp) FindString(s string) string
func (re *Regexp) FindStringIndex(s string) (loc []int)
func (re *Regexp) FindStringSubmatch(s string) []string
func (re *Regexp) FindStringSubmatchIndex(s string) []int
func (re *Regexp) FindSubmatch(b []byte) [][]byte
func (re *Regexp) FindSubmatchIndex(b []byte) []int

有 Index 字样的版本,返回的[]int中会有两个元素,分别是符合项目的字节开头与结尾索引位置,例如:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`foo.?`)
    fmt.Printf("%q\n", re.FindString("seafood fool"))      // "food"
    fmt.Printf("%v\n", re.FindStringIndex("seafood fool")) // [3 7]
}

有 Submatch 字样的方法,是用来支持分组。例如:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(\d{4})-(\d{6})`)
    // ["0970-666888" "0970" "666888"]
    fmt.Printf("%q\n", re.FindStringSubmatch("0970-666888"))
}

如果要找出全部的符合项目呢?在这之前来看看如何用规则表达式来切割子字符串,这可以使用RegexpSplit方法,它的第二个参数可以指定至少切割几个子字符串,若指定小于 0 的数,会切出全部的子字符串:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re, _ := regexp.Compile(`\d`)
    fmt.Println(re.Split("Justin1Monica2Irene", 1))   // [Justin1Monica2Irene]
    fmt.Println(re.Split("Justin1Monica2Irene", 2))   // [Justin Monica2Irene]
    fmt.Println(re.Split("Justin1Monica2Irene", 3))   // [Justin Monica Irene]
    fmt.Println(re.Split("Justin1Monica2Irene", -1))  // [Justin Monica Irene]
}

Regexp提供的 Find 开头的方法,有不少是这种指定模式,例如:

func (re *Regexp) FindAll(b []byte, n int) [][]byte
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
func (re *Regexp) FindAllString(s string, n int) []string
func (re *Regexp) FindAllStringIndex(s string, n int) [][]int
func (re *Regexp) FindAllStringSubmatch(s string, n int) [][]string
func (re *Regexp) FindAllStringSubmatchIndex(s string, n int) [][]int
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int

因此,要找出全部的符合项目,一个例子如下:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(\d{4})-(\d{6})`)

    // 分行显示 "0970-666888" 与 "0970-168168"
    for _, submatch := range re.FindAllString("0970-666888, 0970-168168", -1) {
        fmt.Printf("%q\n", submatch)
    }
}

底下则是捕捉分组的版本:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(\d{4})-(\d{6})`)

    // 分行显示 "0970-666888" 与 "0970-168168"
    for _, submatch := range re.FindAllStringSubmatch("0970-666888, 0970-168168", -1) {
        fmt.Printf("%q\n", submatch)
    }
}

取代相符项目

若要进行取代,使用的是 Replace 开头的方法:

func (re *Regexp) ReplaceAll(src, repl []byte) []byte
func (re *Regexp) ReplaceAllFunc(src []byte, repl func([]byte) []byte) []byte
func (re *Regexp) ReplaceAllLiteral(src, repl []byte) []byte
func (re *Regexp) ReplaceAllLiteralString(src, repl string) string
func (re *Regexp) ReplaceAllString(src, repl string) string
func (re *Regexp) ReplaceAllStringFunc(src string, repl func(string) string) string

有 Func 结尾的方法,表示可以指定函数,该函数接收符合的项目,由函数决定用什么取代。没有 Literal 字样的方法,repl的部份支持分组捕捉,分组计数表示方式是${n},例如:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(^[a-zA-Z]+\d*)@([a-z]+?.)com`)
    // 显示 caterpillar@openhome.cc
    fmt.Println(re.ReplaceAllString("caterpillar@openhome.com", "${1}@${2}cc"))
}

如果使用了(?P<name>…)为分组命名,可以使用${name},例如:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<user>^[a-zA-Z]+\d*)@(?P<preCom>[a-z]+?.)com`)
    // 显示 caterpillar@openhome.cc
    fmt.Println(re.ReplaceAllString("caterpillar@openhome.com", "${user}@${preCom}cc"))
}

虽然说方才的${2}也可以写为$2,然而之后接上其他文本的话,例如$2cc,就会被认为是分组命名,类似地,方才的${preCom}写成$preCom也可以,不过之后接上其他文本的话,例如$preComcc就会被认为名称是 preComcc,建议还是加上{}

Replace 方法中具有 Literal 字样的,就是直接把$当成字面文本来解释:

package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(?P<user>^[a-zA-Z]+\d*)@(?P<preCom>[a-z]+?.)com`)
    // $user@${preCom}cc
    fmt.Println(re.ReplaceAllLiteralString("caterpillar@openhome.com", "$user@${preCom}cc"))
}




展开阅读全文