天天看點

我了解的正規表達式

寫正規表達式時,可以遵循這樣的編寫思路:什麼位置,什麼樣的關鍵詞,有幾個,怎麼做。

一、前言

當我們想以特定的規則從字元串中比對出想要的子串時,正規表達式非常有用。而且,大部分程式設計語言都內建了正規表達式,我們學會它的文法規則後,可以直接在各種程式設計語言中運用。

正規表達式大緻有以下應用:

  • 資料驗證(例如:檢查時間字元串是否格式正确)
  • 資料抓取(例如:網頁抓取,以特定順序查找包含特定單詞集的所有頁面)
  • 資料整理(例如:将原始資料轉換為另一種格式)
  • 字元串解析(例如:捕獲所有 URL GET 參數,捕獲一組括号内的文本)
  • 字元串替換、文法高亮、檔案重命名、資料包嗅探

在實際場景中,被比對的字元串内容雜亂無章,但稍加分析,會發現其實質要麼是

ASCII

碼,要麼就是實作了

unicode

的各種編碼。

unicode

相容

ASCII

,而這些編碼有個特點,在特定的二進制範圍中表示的字元有着相同的特性。比如

ASCII

中的

0-31

位表示一些控制字元,

32-127

位則是一些可列印出來的字元,不一而足。

由此不難想見,我們可以針對不同類别字元的特性,制定一些規則。而正規表達式正是使用一些特殊的元字元構造出了自己的一套比對規則,進而比對出任意形式的資料。這些元字元本來沒有什麼特殊,但經過正規表達式引擎的編譯,便具有了特殊的功能,看似不知所雲,但實際記錄了很多的資訊。

正則通常用

//

包裹,後面跟上若幹個比對标志,比如:

/a[bc]+/gm

一個正規表達式,大緻遵循這樣的編寫思路:什麼位置,什麼樣的關鍵詞,有幾個,怎麼做,那麼上面的這個正則可以這樣解讀:「以全局、多行的模式比對出任意位置的這樣一個字元串:

a

後面跟着一個或多個

b

c

」。

下面按照這個思路分層次講解,其中給出的用例可以使用這個線上工具測試,如網絡不暢可使用這個離線工具測試。

二、編寫思路

1. 什麼位置

1.1 關鍵詞位于行開頭(

^

)或行結尾(

$

^

習慣上讀作

Cat

$

讀作

Dollar

正規表達式 含義說明

ok

比對字元串

ok

^ok

ok

,而且

ok

前面是行開頭。

ok$

ok

ok

後面是行結尾。

^ok$

ok

ok

前面是行開頭,

ok

1.2 指定關鍵詞前後的字元内容

(?=)

(?<=)

(?<=r)ok

ok

前面是

r

,結果不捕獲

r

ok(?=r)

ok

後面是

r

r

這裡可以将

=

替換為

!

,表示否定。

ok(?!r)

ok

後面不是

r

,但結果不捕獲

r

(?<!r)ok

ok

前面不是

r

r

2. 什麼樣的關鍵詞

2.1 單字元關鍵詞

2.1.1 限定單字元關鍵詞的備選值:用

[]

包裹

[abc]

包含

abc

中的一個,等價于

a|b|c

[a-c]

同上。

[a-fA-F0-9]

單個十六進制數字的字元串,且不區分大小寫。

[0-9]%

%

前面是一個數字。

[^a-zA-Z]

不在

a~Z

的範圍内。在這個例子中,

^

表示否定。

2.1.2 元字元轉義

看到這裡,我們知道

^$<>()[]{}|/\.+*?:=!

這些元字元在正則中可能有着特殊的作用。那麼這裡會有個問題,正規表達式是用字元串來描述比對規則,進而去比對字元串。如果我們需要比對這些元字元本身該怎麼辦呢?

這時可以在這些字元前添加轉義符号

\

,使其還原為字元本身,不再具備限定含義。

\$\d

$

後面跟着一個數字字元。

2.2 多字元關鍵詞

2.2.1 構造關鍵詞元組:用

()

之前的例子中,隻能對單字元關鍵詞進行限定,如果要對多字元關鍵詞進行限定,可以用

()

括起來,構造成一個關鍵詞元組,看下面的例子。

a(bc)

a

後面跟着一個

bc

a(?:bc)*

a

後面不能跟着一個

bc

a(?<foo>bc)

a

bc

,并将

bc

這個元組命名為

foo

當我們自己的程式設計語言從字元串或文本資料中比對資訊時,像這樣構造關鍵詞元組非常有用。這樣的比對結果會傳回一個清單,進而友善我們根據下标去取值。

而如果使用的是上表的第三種,給關鍵詞元組命名的方式,傳回的結果則是字典。鍵的名稱是組名

foo

,值是比對結果清單。

2.2.2 引用關鍵詞元組:

\1

([abc])\1

引用第一個關鍵詞元組

([abc])

比對的相同文本。

([abc])([de])\2\1

\2

引用第二個關鍵詞元組

([de])

(?<foo>[abc])\k<foo>

\k<foo>

引用前面名為

foo

的關鍵詞元組

(?<foo>[abc])

比對的相同文本。結果與

([abc])\1

相同。

2.3 特定類型關鍵詞

.

比對任意字元,當比對标志不是

s

時,不比對

\n

\d

比對數字。

\D

比對非數字。

\b

表示它的一邊是單詞字元,一邊不是單詞字元。如

\bok\b

ok

\n

空格

這樣的非單詞字元,後面也是非單詞字元。

\B

比對

\b

所有不比對的内容。如

\Bok\B

ok

前面是單詞字元,後面也是單詞字元。

\w

比對字母、數字、下劃線。

\W

比對非字母、非數字、非下劃線。

\s

比對空白字元,包括空格( )、水準制表符(

\t

)、換行符(

\n

)、換頁符(

\f

)、回車符(

\r

)、垂直制表符(

\v

)、空字元(

\0

)。

\S

比對非空白字元。

類似

\d

這樣的限定方法,當

\

後面的字母為大寫時,表示的限定規則恰好與小寫相反。

3. 有幾個

3.1 指定關鍵詞的數量:

*

+

?

{}

符号 含義 數學區間

*

比對零個或多個。

x ∈ Z

x = 1

x ∈ [0, +∞)

+

比對一個或多個。

x ∈ Z

x = 1

x ∈ [1, +∞)

?

比對零個或一個。

x ∈ Z

x = 0

x = 1

{a,b}

a~b

個。

x ∈ Z

x ∈ [a, b]

限定數量時,限定條件須寫在關鍵詞的後面,看下面的例子:

abc*

ab

後面跟着零個或多個

c

abc+

ab

c

abc?

ab

後面跟着零個或一個

c

abc{2}

ab

後面跟着兩個

c

abc{2,}

ab

後面跟着兩個或兩個以上 的

c

abc{2,5}

ab

後面跟着兩個到五個

c

a(bc)*

ab

bc

a(bc){2,5}

ab

bc

這裡還可以加入或操作符

|

[]

,更靈活地指定數量。

a(b|c)

a

。要求

a

後面跟着

b

c

a[bc]

ab

ac

a

b

c

這兩者都要求

a

後面是備選詞

b

c

,差別在于,前者不捕獲備選詞,後者捕獲。

4. 怎麼做

4.1 一些比對标志碼

在執行比對時,可以通過設定标志碼來改變比對的模式:

标志碼 模式含義 備注

g

全局模式 查找所有的比對項。

m

多行模式 使邊界字元

^

$

比對每一行的開頭和結尾,記住是多行,而不是整個字元串的開頭和結尾。

i

忽略大小寫 将比對設定為不區分大小寫,搜尋時不區分大小寫:

A

a

沒有差別。

s

單行模式 在其他模式中,

.

不比對類似

\n

的控制字元,在這種模式下比對。

U

非貪婪模式 使用非貪婪模式比對。

當未指定非貪婪模式時,上述的數量限定符号

* + {}

,比對時都是貪婪模式,即它們會盡可能地在提供的文本中比對目标值。

例如,在

This is a <div> simple div </div> test

這個例子中,

<.+>

比對到

<div>simple div</div>

.+

意為:一個或多個任意字元,這裡它比對了

div>simple div</div

,但我們的目的明顯是比對出

div

</div>

為了隻捕獲

div

,可以在

.+

之後添加

?

,讓它用非貪婪模式執行:

<.+?>

比對一次或多次

<

>

中的内容,根據需要擴充。

在上述例子中,會比對出

<div<>

這樣的結果,為了更加嚴謹,應該盡量減少

.

的使用,可以優化為:

<[^<>]+>

比對一次或多次這樣的字元:

<

>

中不含

<

>

各種程式設計語言中的正則使用方法

這裡列舉 Python、Java、JavaScript、Golang 的正則使用方式,使用時替換正規表達式即可。

Python

import re
pattern = re.compile(ur'^[0-9]*$')
str = u'12345'
print(pattern.search(str))
           

Java

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexMatches {
	
	public static void main(String args[]) {
		String str = "";
		String pattern = "^[0-9]*$";

		Pattern r = Pattern.compile(pattern);
		Matcher m = r.matcher(str);
		System.out.println(m.matches());
	}

}
           

JavaScript

var pattern = /^[0-9]*$/g,
	str = '12345';
console.log(pattern.test(str));``
           

Golang

package main

import (
	"fmt"
	"regexp"
)

func main() {
	str := ""
	matched, err := regexp.MatchString("^[0-9]*$", str)
	fmt.Println(matched, err)
}
           

參考文章

  • Regex tutorial — A quick cheatsheet by examples
  • 正規表達式 - 教程

繼續閱讀