昨天有同事在看k8s原始碼,突然問了一個看似很簡單的問題,golang.org/pkg/regexp/… 官方文件中ReplaceAllString
的解釋,到底是什麼意思?到底怎麼用?
官方英文原文:
func (re *Regexp) ReplaceAllString(src, repl string) string
ReplaceAllString returns a copy of src, replacing matches of the Regexp with the replacement string repl. Inside repl, $ signs are interpreted as in Expand, so for instance $1 represents the text of the first submatch.
複製程式碼
中文文件:
ReplaceAllLiteral返回src的一個拷貝,將src中所有re的匹配結果都替換為repl。在替換時,repl中的'$'符號會按照Expand方法的規則進行解釋和替換,例如$1會被替換為第一個分組匹配結果。
複製程式碼
看上去一臉懵逼,還是不理解這個函式到底怎麼用。
又去看官方的示例:
Example:
re := regexp.MustCompile("a(x*)b")
fmt.Println(re.ReplaceAllString("-ab-axxb-", "T"))
fmt.Println(re.ReplaceAllString("-ab-axxb-", "$1"))
fmt.Println(re.ReplaceAllString("-ab-axxb-", "$1W"))
fmt.Println(re.ReplaceAllString("-ab-axxb-", "${1}W"))
Output:
-T-T-
--xx-
---
-W-xxW-
複製程式碼
第一個替換勉強能看明白,是用T
去替換-ab-axxb-
中符合正規表示式匹配的部分;第二個中的$
是什麼意思?$1
看起來像是匹配正規表示式分組中第一部分,那$1W
呢?${1}W
呢?帶著這些問題,開始深入研究這個函式到底怎麼用。
首先,$
符號在Expand
函式中有解釋過:
func (re *Regexp) Expand(dst []byte, template []byte, src []byte, match []int) []byte
Expand返回新生成的將template新增到dst後面的切片。在新增時,Expand會將template中的變數替換為從src匹配的結果。match應該是被FindSubmatchIndex返回的匹配結果起止位置索引。(通常就是匹配src,除非你要將匹配得到的位置用於另一個[]byte)
在template引數裡,一個變數表示為格式如:$name或${name}的字串,其中name是長度>0的字母、數字和下劃線的序列。一個單純的數字字元名如$1會作為捕獲分組的數字索引;其他的名字對應(?P<name>...)語法產生的命名捕獲分組的名字。超出範圍的數字索引、索引對應的分組未匹配到文字、正規表示式中未出現的分組名,都會被替換為空切片。
$name格式的變數名,name會盡可能取最長序列:$1x等價於${1x}而非${1}x,$10等價於${10}而非${1}0。因此$name適用在後跟空格/換行等字元的情況,${name}適用所有情況。
如果要在輸出中插入一個字面值'$',在template裡可以使用$$。
複製程式碼
說了這麼多,其實最終要的部分可以概括為三點:
$
後面只有數字,則代表正規表示式的分組索引,關於正規表示式的分組解釋:
捕獲組可以通過從左到右計算其開括號來編號 。例如,在表示式 (A)(B(C)) 中,存在四個這樣的組:
0 | (A)(B(C)) |
---|---|
1 | (A) |
2 | (B(C)) |
3 | (C) |
組零始終代表整個表示式
之所以這樣命名捕獲組是因為在匹配中,儲存了與這些組匹配的輸入序列的每個子序列。捕獲的子序列稍後可以通過 Back 引用(反向引用) 在表示式中使用,也可以在匹配操作完成後從匹配器檢索。
匹配正規表示式的$1
部分,保留該部分,去掉其餘部分;
$
後面是字串,即$name
,代表匹配對應(?P...)語法產生的命名捕獲分組的名字${數字}字串
,即${1}xxx
,意思是匹配正規表示式的分組1,src
中匹配分組1的保留,並刪除src
剩餘部分,追加xxx
,後面會有程式碼示例解釋這部分,也是最難理解的部分- 最簡單的情況,引數
repl
是字串,將src中所有re的匹配結果都替換為repl
下面用程式碼來解釋以上幾種情況:
package main
import (
"fmt"
"regexp"
)
func main() {
s := "Hello World, 123 Go!"
//定義一個正規表示式reg,匹配Hello或者Go
reg := regexp.MustCompile(`(Hell|G)o`)
s2 := "2019-12-01,test"
//定義一個正規表示式reg2,匹配 YYYY-MM-DD 的日期格式
reg2 := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
//最簡單的情況,用“T替換”"-ab-axxb-"中符合正則"a(x*)b"的部分
reg3 := regexp.MustCompile("a(x*)b")
fmt.Println(re.ReplaceAllString("-ab-axxb-", "T"))
//${1}匹配"Hello World, 123 Go!"中符合正則`(Hell|G)`的部分並保留,去掉"Hello"與"Go"中的'o'並用"ddd"追加
rep1 := "${1}ddd"
fmt.Printf("%q\n", reg.ReplaceAllString(s, rep1))
//首先,"2019-12-01,test"中符合正規表示式`(\d{4})-(\d{2})-(\d{2})`的部分是"2019-12-01",將該部分匹配'(\d{4})'的'2019'保留,去掉剩餘部分
rep2 := "${1}"
fmt.Printf("%q\n", reg2.ReplaceAllString(s2,rep2))
//首先,"2019-12-01,test"中符合正規表示式`(\d{4})-(\d{2})-(\d{2})`的部分是"2019-12-01",將該部分匹配'(\d{2})'的'12'保留,去掉剩餘部分
rep3 := "${2}"
fmt.Printf("%q\n", reg2.ReplaceAllString(s2,rep3))
//首先,"2019-12-01,test"中符合正規表示式`(\d{4})-(\d{2})-(\d{2})`的部分是"2019-12-01",將該部分匹配'(\d{2})'的'01'保留,去掉剩餘部分,並追加"13:30:12"
rep4 := "${3}:13:30:12"
fmt.Printf("%q\n", reg2.ReplaceAllString(s2,rep4))
}
複製程式碼
上面程式碼輸出依次是:
$ go run main.go
-T-T-
"Hellddd World, 123 Gddd!"
"2019,test"
"12,test"
"01:13:30:12,test"
複製程式碼
總結
Goregexp
包中的ReplaceAllString
設計有些許反人類,理解和使用上感覺不方便,如果你有更好的理解或者示例程式碼,Call me!