詳解Go regexp包中 ReplaceAllString 的用法

昨天有同事在看k8s源碼,忽然問了一個看似很簡單的問題,golang.org/pkg/regexp/… 官方文檔中ReplaceAllString的解釋,究竟是什麼意思?到底怎麼用?golang

官方英文原文:正則表達式

func (re *Regexp) ReplaceAllString(src, repl string) stringshell

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.

複製代碼

中文文檔:bash

ReplaceAllLiteral返回src的一個拷貝,將src中全部re的匹配結果都替換爲repl。在替換時,repl中的'$'符號會按照Expand方法的規則進行解釋和替換,例如$1會被替換爲第一個分組匹配結果。
複製代碼

看上去一臉懵逼,仍是不理解這個函數到底怎麼用。ide

又去看官方的示例:函數

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呢?帶着這些問題,開始深刻研究這個函數到底怎麼用。ui

首先,$符號在Expand函數中有解釋過:spa

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裏可使用$$。
複製代碼

說了這麼多,其實最終要的部分能夠歸納爲三點:設計

  1. $後面只有數字,則表明正則表達式的分組索引,關於正則表達式的分組解釋:

捕獲組能夠經過從左到右計算其開括號來編號 。例如,在表達式 (A)(B(C)) 中,存在四個這樣的組:code

0 (A)(B(C))
1 (A)
2 (B(C))
3 (C)

組零始終表明整個表達式

之因此這樣命名捕獲組是由於在匹配中,保存了與這些組匹配的輸入序列的每一個子序列。捕獲的子序列稍後能夠經過 Back 引用(反向引用) 在表達式中使用,也能夠在匹配操做完成後從匹配器檢索。

匹配正則表達式的$1部分,保留該部分,去掉其他部分;

  1. $後面是字符串,即$name,表明匹配對應(?P...)語法產生的命名捕獲分組的名字
  2. ${數字}字符串,即${1}xxx,意思是匹配正則表達式的分組1,src中匹配分組1的保留,並刪除src剩餘部分,追加xxx,後面會有代碼示例解釋這部分,也是最難理解的部分
  3. 最簡單的狀況,參數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!

相關文章
相關標籤/搜索