天天看點

go正則處理

正規表達式是一種進行模式比對和文本操縱的複雜而又強大的工具。雖然正規表達式比純粹的文本比對效率低,但是它卻更靈活。按照它的文法規則,随需構造出的比對模式就能夠從原始文本中篩選出幾乎任何想你要得到的字元組合。如果你在Web開發中需要從一些文本資料源中擷取資料,那麼你隻需要按照它的文法規則,随需構造出正确的模式字元串就能夠從原資料源提取出有意義的文本資訊。

Go語言通過

regexp

标準包為正規表達式提供了官方支援,如果你已經使用過其他程式設計語言提供的正則相關功能,那麼你應該對Go語言版本的不會太陌生,但是它們之間也有一些小的差異,因為Go實作的是RE2标準,除了\C,詳細的文法描述參考:

http://code.google.com/p/re2/wiki/Syntax

其實字元串處理我們可以使用

strings

包來進行搜尋(Contains、Index)、替換(Replace)和解析(Split、Join)等操作,但是這些都是簡單的字元串操作,他們的搜尋都是大小寫敏感,而且固定的字元串,如果我們需要比對可變的那種就沒辦法實作了,當然如果

strings

包能解決你的問題,那麼就盡量使用它來解決。因為他們足夠簡單、而且性能和可讀性都會比正則好。

如果你還記得,在前面表單驗證的小節裡,我們已經接觸過正則處理,在那裡我們利用了它來驗證輸入的資訊是否滿足某些預設的條件。在使用中需要注意的一點就是:所有的字元都是UTF-8編碼的。接下來讓我們更加深入的來學習Go語言的

regexp

包相關知識吧。

通過正則判斷是否比對

regexp

包中含有三個函數用來判斷是否比對,如果比對傳回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)
           

上面的三個函數實作了同一個功能,就是判斷

pattern

是否和輸入源比對,比對的話就傳回true,如果解析正則出錯則傳回error。三個函數的輸入源分别是byte slice、RuneReader和string。

如果要驗證一個輸入是不是IP位址,那麼如何來判斷呢?請看如下實作

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和我們平常使用的正則一模一樣。再來看一個例子:當使用者輸入一個字元串,我們想知道是不是一次合法的輸入:

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("數字")
    } else {
        fmt.Println("不是數字")
    }
}
           

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

通過正則擷取内容

Match模式隻能用來對字元串的判斷,而無法截取字元串的一部分、過濾字元串、或者提取出符合條件的一批字元串。如果想要滿足這些需求,那就需要使用正規表達式的複雜模式。

我們經常需要一些爬蟲程式,下面就以爬蟲為例來說明如何使用正則來過濾或截取抓取到的資料:

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标簽全轉換成小寫
    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, "")

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

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

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

從這個示例可以看出,使用複雜的正則首先是Compile,它會解析正規表達式是否合法,如果正确,那麼就會傳回一個Regexp,然後就可以利用傳回的Regexp在任意的字元串上面執行需要的操作。

解析正規表達式的有如下幾個方法:

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

CompilePOSIX和Compile的不同點在于POSIX必須使用POSIX文法,它使用最左最長方式搜尋,而Compile是采用的則隻采用最左方式搜尋(例如[a-z]{2,4}這樣一個正規表達式,應用于"aa09aaa88aaaa"這個文本串時,CompilePOSIX傳回了aaaa,而Compile的傳回的是aa)。字首有Must的函數表示,在解析正則文法的時候,如果比對模式串不滿足正确的文法則直接panic,而不加Must的則隻是傳回錯誤。

在了解了如何建立一個Regexp之後,我們再來看一下這個struct提供了哪些方法來輔助我們操作字元串,首先我們來看下面這些用來搜尋的函數:

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個函數我們根據輸入源(byte slice、string和io.RuneReader)不同還可以繼續簡化成如下幾個,其他的隻是輸入源不一樣,其他功能基本是一樣的:

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
           

對于這些函數的使用我們來看下面這個例子

package main

import (
    "fmt"
    "regexp"
)

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

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

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

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

    //查找符合條件的index位置,開始位置和結束位置
    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,傳回數組,第一個元素是比對的全部元素,第二個元素是第一個()裡面的,第三個是第二個()裡面的
    //下面的輸出第一個元素是"am learning Go language"
    //第二個元素是" learning Go ",注意包含空格的輸出
    //第三個元素是"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)
}
           

前面介紹過比對函數,Regexp也定義了三個函數,它們和同名的外部函數功能一模一樣,其實外部函數就是調用了這Regexp的三個函數來實作的:

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

接下裡讓我們來了解替換函數是怎麼操作的?

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 (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
           
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))
}
           

繼續閱讀