Go 正則處理

2022-05-13 17:43 更新

正則表達(dá)式是一種進(jìn)行模式匹配和文本操縱的復(fù)雜而又強(qiáng)大的工具。雖然正則表達(dá)式比純粹的文本匹配效率低,但是它卻更靈活。按照它的語(yǔ)法規(guī)則,隨需構(gòu)造出的匹配模式就能夠從原始文本中篩選出幾乎任何想你要得到的字符組合。如果你在Web開發(fā)中需要從一些文本數(shù)據(jù)源中獲取數(shù)據(jù),那么你只需要按照它的語(yǔ)法規(guī)則,隨需構(gòu)造出正確的模式字符串就能夠從原數(shù)據(jù)源提取出有意義的文本信息。

Go語(yǔ)言通過(guò)regexp標(biāo)準(zhǔn)包為正則表達(dá)式提供了官方支持,如果你已經(jīng)使用過(guò)其他編程語(yǔ)言提供的正則相關(guān)功能,那么你應(yīng)該對(duì)Go語(yǔ)言版本的不會(huì)太陌生,但是它們之間也有一些小的差異,因?yàn)镚o實(shí)現(xiàn)的是RE2標(biāo)準(zhǔn),除了\C,詳細(xì)的語(yǔ)法描述參考:http://code.google.com/p/re2/wiki/Syntax

其實(shí)字符串處理我們可以使用strings包來(lái)進(jìn)行搜索(Contains、Index)、替換(Replace)和解析(Split、Join)等操作,但是這些都是簡(jiǎn)單的字符串操作,他們的搜索都是大小寫敏感,而且固定的字符串,如果我們需要匹配可變的那種就沒(méi)辦法實(shí)現(xiàn)了,當(dāng)然如果strings包能解決你的問(wèn)題,那么就盡量使用它來(lái)解決。因?yàn)樗麄冏銐蚝?jiǎn)單、而且性能和可讀性都會(huì)比正則好。

如果你還記得,在前面表單驗(yàn)證的小節(jié)里,我們已經(jīng)接觸過(guò)正則處理,在那里我們利用了它來(lái)驗(yàn)證輸入的信息是否滿足某些預(yù)設(shè)的條件。在使用中需要注意的一點(diǎn)就是:所有的字符都是UTF-8編碼的。接下來(lái)讓我們更加深入的來(lái)學(xué)習(xí)Go語(yǔ)言的regexp包相關(guān)知識(shí)吧。

通過(guò)正則判斷是否匹配

regexp包中含有三個(gè)函數(shù)用來(lái)判斷是否匹配,如果匹配返回true,否則返回false

func Match(pattern string, b []byte) (matched bool, error error)
func MatchReader(pattern string, r io.RuneReader) (matched bool, error error)
func MatchString(pattern string, s string) (matched bool, error error)

上面的三個(gè)函數(shù)實(shí)現(xiàn)了同一個(gè)功能,就是判斷pattern是否和輸入源匹配,匹配的話就返回true,如果解析正則出錯(cuò)則返回error。三個(gè)函數(shù)的輸入源分別是byte slice、RuneReader和string。

如果要驗(yàn)證一個(gè)輸入是不是IP地址,那么如何來(lái)判斷呢?請(qǐng)看如下實(shí)現(xiàn)

func IsIP(ip string) (b bool) {
    if m, _ := regexp.MatchString("^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$", ip); !m {
        return false
    }
    return true
}

可以看到,regexp的pattern和我們平常使用的正則一模一樣。再來(lái)看一個(gè)例子:當(dāng)用戶輸入一個(gè)字符串,我們想知道是不是一次合法的輸入:

func main() {
    if len(os.Args) == 1 {
        fmt.Println("Usage: regexp [string]")
        os.Exit(1)
    } else if m, _ := regexp.MatchString("^[0-9]+$", os.Args[1]); m {
        fmt.Println("數(shù)字")
    } else {
        fmt.Println("不是數(shù)字")
    }
}

在上面的兩個(gè)小例子中,我們采用了Match(Reader|String)來(lái)判斷一些字符串是否符合我們的描述需求,它們使用起來(lái)非常方便。

通過(guò)正則獲取內(nèi)容

Match模式只能用來(lái)對(duì)字符串的判斷,而無(wú)法截取字符串的一部分、過(guò)濾字符串、或者提取出符合條件的一批字符串。如果想要滿足這些需求,那就需要使用正則表達(dá)式的復(fù)雜模式。

我們經(jīng)常需要一些爬蟲程序,下面就以爬蟲為例來(lái)說(shuō)明如何使用正則來(lái)過(guò)濾或截取抓取到的數(shù)據(jù):

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "regexp"
    "strings"
)

func main() {
    resp, err := http.Get("http://www.baidu.com")
    if err != nil {
        fmt.Println("http get error.")
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("http read error")
        return
    }

    src := string(body)

    //將HTML標(biāo)簽全轉(zhuǎn)換成小寫
    re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
    src = re.ReplaceAllStringFunc(src, strings.ToLower)

    //去除STYLE
    re, _ = regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")
    src = re.ReplaceAllString(src, "")

    //去除SCRIPT
    re, _ = regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")
    src = re.ReplaceAllString(src, "")

    //去除所有尖括號(hào)內(nèi)的HTML代碼,并換成換行符
    re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
    src = re.ReplaceAllString(src, "\n")

    //去除連續(xù)的換行符
    re, _ = regexp.Compile("\\s{2,}")
    src = re.ReplaceAllString(src, "\n")

    fmt.Println(strings.TrimSpace(src))
}

從這個(gè)示例可以看出,使用復(fù)雜的正則首先是Compile,它會(huì)解析正則表達(dá)式是否合法,如果正確,那么就會(huì)返回一個(gè)Regexp,然后就可以利用返回的Regexp在任意的字符串上面執(zhí)行需要的操作。

解析正則表達(dá)式的有如下幾個(gè)方法:

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

CompilePOSIX和Compile的不同點(diǎn)在于POSIX必須使用POSIX語(yǔ)法,它使用最左最長(zhǎng)方式搜索,而Compile是采用的則只采用最左方式搜索(例如[a-z]{2,4}這樣一個(gè)正則表達(dá)式,應(yīng)用于"aa09aaa88aaaa"這個(gè)文本串時(shí),CompilePOSIX返回了aaaa,而Compile的返回的是aa)。前綴有Must的函數(shù)表示,在解析正則語(yǔ)法的時(shí)候,如果匹配模式串不滿足正確的語(yǔ)法則直接panic,而不加Must的則只是返回錯(cuò)誤。

在了解了如何新建一個(gè)Regexp之后,我們?cè)賮?lái)看一下這個(gè)struct提供了哪些方法來(lái)輔助我們操作字符串,首先我們來(lái)看下面這些用來(lái)搜索的函數(shù):

func (re *Regexp) Find(b []byte) []byte
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
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

上面這18個(gè)函數(shù)我們根據(jù)輸入源(byte slice、string和io.RuneReader)不同還可以繼續(xù)簡(jiǎn)化成如下幾個(gè),其他的只是輸入源不一樣,其他功能基本是一樣的:

func (re *Regexp) Find(b []byte) []byte
func (re *Regexp) FindAll(b []byte, n int) [][]byte
func (re *Regexp) FindAllIndex(b []byte, n int) [][]int
func (re *Regexp) FindAllSubmatch(b []byte, n int) [][][]byte
func (re *Regexp) FindAllSubmatchIndex(b []byte, n int) [][]int
func (re *Regexp) FindIndex(b []byte) (loc []int)
func (re *Regexp) FindSubmatch(b []byte) [][]byte
func (re *Regexp) FindSubmatchIndex(b []byte) []int

對(duì)于這些函數(shù)的使用我們來(lái)看下面這個(gè)例子

package main

import (
    "fmt"
    "regexp"
)

func main() {
    a := "I am learning Go language"

    re, _ := regexp.Compile("[a-z]{2,4}")

    //查找符合正則的第一個(gè)
    one := re.Find([]byte(a))
    fmt.Println("Find:", string(one))

    //查找符合正則的所有slice,n小于0表示返回全部符合的字符串,不然就是返回指定的長(zhǎng)度
    all := re.FindAll([]byte(a), -1)
    fmt.Println("FindAll", all)

    //查找符合條件的index位置,開始位置和結(jié)束位置
    index := re.FindIndex([]byte(a))
    fmt.Println("FindIndex", index)

    //查找符合條件的所有的index位置,n同上
    allindex := re.FindAllIndex([]byte(a), -1)
    fmt.Println("FindAllIndex", allindex)

    re2, _ := regexp.Compile("am(.*)lang(.*)")

    //查找Submatch,返回?cái)?shù)組,第一個(gè)元素是匹配的全部元素,第二個(gè)元素是第一個(gè)()里面的,第三個(gè)是第二個(gè)()里面的
    //下面的輸出第一個(gè)元素是"am learning Go language"
    //第二個(gè)元素是" learning Go ",注意包含空格的輸出
    //第三個(gè)元素是"uage"
    submatch := re2.FindSubmatch([]byte(a))
    fmt.Println("FindSubmatch", submatch)
    for _, v := range submatch {
        fmt.Println(string(v))
    }

    //定義和上面的FindIndex一樣
    submatchindex := re2.FindSubmatchIndex([]byte(a))
    fmt.Println(submatchindex)

    //FindAllSubmatch,查找所有符合條件的子匹配
    submatchall := re2.FindAllSubmatch([]byte(a), -1)
    fmt.Println(submatchall)

    //FindAllSubmatchIndex,查找所有字匹配的index
    submatchallindex := re2.FindAllSubmatchIndex([]byte(a), -1)
    fmt.Println(submatchallindex)
}

前面介紹過(guò)匹配函數(shù),Regexp也定義了三個(gè)函數(shù),它們和同名的外部函數(shù)功能一模一樣,其實(shí)外部函數(shù)就是調(diào)用了這Regexp的三個(gè)函數(shù)來(lái)實(shí)現(xiàn)的:

func (re *Regexp) Match(b []byte) bool
func (re *Regexp) MatchReader(r io.RuneReader) bool
func (re *Regexp) MatchString(s string) bool

接下里讓我們來(lái)了解替換函數(shù)是怎么操作的?

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

這些替換函數(shù)我們?cè)谏厦娴淖ゾW(wǎng)頁(yè)的例子有詳細(xì)應(yīng)用示例,

接下來(lái)我們看一下Expand的解釋:

func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte
func (re *Regexp) ExpandString(dst []byte, template string, src string, match []int) []byte

那么這個(gè)Expand到底用來(lái)干嘛的呢?請(qǐng)看下面的例子:

func main() {
    src := []byte(`
        call hello alice
        hello bob
        call hello eve
    `)
    pat := regexp.MustCompile(`(?m)(call)\s+(?P<cmd>\w+)\s+(?P<arg>.+)\s*$`)
    res := []byte{}
    for _, s := range pat.FindAllSubmatchIndex(src, -1) {
        res = pat.Expand(res, []byte("$cmd('$arg')\n"), src, s)
    }
    fmt.Println(string(res))
}

至此我們已經(jīng)全部介紹完Go語(yǔ)言的regexp包,通過(guò)對(duì)它的主要函數(shù)介紹及演示,相信大家應(yīng)該能夠通過(guò)Go語(yǔ)言的正則包進(jìn)行一些基本的正則的操作了。

以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)