您現在的位置是:首頁 > 籃球
轉:在PHP中寫出更好的正則表示式
- 由 葉LS 發表于 籃球
- 2021-12-31
正則表示式中怎麼找替某字的片語
正則表示式
很強大。但眾所周知,正則表示式的可讀性是比較差的。維護已經寫好的正則表示式更是不容易。
PHP 支援 PCRE(從 PHP 7。3 開始使用 PCRE2)正則表示式。PCRE 正則表示式支援一些高階特性。透過它們可以寫出更易讀、自解釋(self-explanatory),而且易維護的正則表示式。PHP 的 filters 和 ctype 函式族 提供了諸如 URL 校驗、郵箱校驗、和字母數字值校驗等功能。有了它們,我們也可以少用一些正則表示式。
IDE 支援正則表示式作語法高亮,這對我們理解正則表示式有很大的幫助。IDE 甚至還能幫我們快速修正一些錯誤。但從長期來看,我們還是要編寫自解釋的和可讀性更強的正則表示式。
下面會介紹一些在 PHP 中改進正則表示式的技巧。注意,有些特性老版本(7。3之前)的PHP可能不支援。而且,這些使用這些特性可能導致寫出來的正則表示式不容易移除到其他語言。譬如說,JavaScript 就不支援命名捕獲功能(連舊版 PHP 都支援)。JavaScript 直到 ECMAScript 2018 標準才加入該功能。
選擇分割符
正則表示式分為表示式和引數兩部分。表示式兩邊有兩個分割符,後面跟著可選的引數。
大家看下面的正則表示式:
/(foo|bar)/i
任何一個正則表示式都是用分割符包住表示式內容,後面接著引數。引數是可選的。在上例中,
(foo|bar)
就是表示式本身,
i
是引數或都修飾符,
/
就是分割符。
雖然最常用見分割符是斜槓(
/
),我們還可以使用諸如
~
,
!
,
@
,
#
,
$
等符號。但不能使用數字(0-9)、字母(a-zA-Z)、多位元組字元(比如 Emoji 表情)和反斜槓(
\
)。
另外,括號也可以當成分割符。大括號(
{}
)、小括號(
()
)、方括號(
[]
)和尖括號(
<>
)都能用,在某些場景下可以提高可讀性。
如果正則表示式內容需要使用分割符,那一定要對其進行轉義。所以,如何選擇合適的分割符就變得非常重要了。轉義越少,越容易理解。避免使用正則元字元(諸如
^
,
$
, 括號和其他在正則中有特殊含義的字元)可以減少跳脫字元的數量。
雖說斜槓很常用,但它不適合用在處理 URI 相關的正則表示式。
preg_match(‘/^https:\/\/imoniang。com\/path/i’, $uri);
在上例中,因為表示式包含很多斜槓,分割符也是斜槓,所以表示式中的斜槓就得轉義。這讓降低了正則的可讀性。
只須將分割符從
/
變成
#
就可以去掉所有轉義,從而讓正則更容易閱讀:
- /^https:\/\/imoniang。com\/path/i+ #^https://imoniang。com/path#i- preg_match(‘/^https:\/\/imoniang。com\/path/i’, $uri);+ preg_match(‘#^https://imoniang。com/path#i’, $uri);
避免跳脫字元
除了選擇適當的分割符之外,還有一些方法來避免轉義。
在正則中,方括號內的元字元不需要轉義。例如,
。
,
*
,
+
和
$
都是有特殊功能的元字元,但在方括號內就是普通字元。
/Username: @[a-z\。0-9]/
在上面的正則中,句點(
。
)被轉義成
\。
,其實沒有必要。在方括號內,
。
就是句點,沒有特殊含義,不必轉義。
再如,如果某個字元不是範圍表示式的一部分,那也不需要轉義。
舉個例子。連字元(
-
)跟兩個字元連用可以表示字元範圍。如果連字元兩邊沒有字元,那就沒有特殊含義。在正則
/[A-Z]/
中,連字元
-
用於表示從
A
到
Z
這個範圍的字元。如果我們對連字元作轉義(
/[A\-Z]/
),那就只能匹配字元
A
,
Z
和
-
。如果不用轉義,而是把連字元放到方括號內容的最後面,效果是一樣的。正則
/[A\-Z]/
跟
/[AZ-]/
是等效的,但後者更易讀。
轉義用太多不會影響正則的語義,卻會降低可讀性。
- /Price: [0-9\-\$\。\+]+/+ /Price: [0-9$。+-]+/
有一個引數
x
,開啟之後,如果一個字元不需要轉義但我們對其進行轉義,就會報錯。但功能有限,因為它沒法識別上下文語義(比如,不能根據是在括號內丟擲錯誤等)。
preg_match(‘/x\yz/X’, ‘’); // “y” is not a special character, but escaped。Warning: preg_match(): Compilation failed: unrecognized character follows \ at offset 2 in 。。。 on line 。。。
非捕獲分組
在正則表示式中,圓括號
()
會開啟一個捕獲組。被圓括號內的正則匹配的內容會儲存到匹配列表中:
下面是一個例子,演示如何從文字
Price: ¥55
提取價格:
$pattern = ‘/Price: (€|¥)(\d+)/’;$text = ‘Price: ¥55’;preg_match($pattern, $text, $matches);
上例中有兩個捕獲組:第一個是捕獲貨幣符號
(€|¥)
,後面一個是捕獲數字。
變數
$matches
儲存了兩個捕獲組的匹配內容:
var_dump($matches);array(3) { [0]=> string(12) “Price: ¥55” [1]=> string(3) “¥” [2]=> string(2) “55”}
如果不想捕獲或者不想知道捕獲的結果,那就需要非捕獲分組。
非捕獲分組的語法是在第一個括號後面加上
?:
。正則引擎會確保內容匹配括號內的規則,但不會將匹配的內容加入結果列表,即不會捕獲。
如果前面的正則只想獲取數字,那麼
(€|¥)
就可以改為非捕獲分組
(?:€|¥)
。
$pattern = ‘/Price: (?:€|¥)(\d+)/’;$text = ‘Price: ¥55’;preg_match($pattern, $text, $matches);var_dump($matches);array(2) { [0]=> string(12) “Price: ¥55” [1]=> string(2) “55”}
如果正則中有多個分組,使用非捕獲分組可以減少
$matches
中的資料量。
命名捕獲
命名捕獲在語法上跟非捕獲分組有點像,但它可以捕獲內容並給內容指定一個名字。這不緊是給匹配結果加上名字,更是給匹配模式加上名字。
我們再次以上面的正則為例進行說明。每個捕獲分組都可以指定名字:
/Price: (?
命名語法是在分組括號後面加
?
。
在上例中,
(?
的名字是
currency
,
(?
的名字是
price
。這些名字一定程度上為正則表態式提供了更多的上下文資訊,而且也讓大家能用名字讀取匹配結果。
$pattern = ‘/Price: (?
現在變數
$matches
同時包含分組編號和分組名字兩類匹配結果。
使用命名捕獲分組,我們可以更容易地從
$matches
提取內容。而且後面只要不改分組名稱,就能比較容易地修改正則表示式的內容。
預設情況下捕獲組不能重名。如果重名會報錯
PHP Warning: preg_match(): Compilation failed: two named subpatterns have the same name (PCRE2_DUPNAMES not set) at offset 。。。 in 。。。 on line 。。。
。如果非要重名,可以使用
J
引數:
/Price: (?
在這個正則中有兩個分組都就
currency
,而且用
J
引數指明允許重複。在匹配的時候,命名分組只會返回最後的匹配結果,但按編號分組的仍然會返回所有內容:
$pattern = ’/Price: (?
寫註釋
有些正則很長,甚至需要寫好多行。
我們可以一次寫一部分匹配模式,然後將它們連線起來。我們還可以為每一部分添加註釋。這樣既提高可讀性,又能降低程式碼評審的難度。
- $pattern = ’/Price: (?
此外,還可以在正則表示式內部添加註釋。
有一個引數叫
x
,可以讓引擎忽略正則中所有的空白字元。這樣就能使用空白字元或者換行符重新組織正則表示式了。
- /Price: (?
在正則
/Price: (?
中,引擎會檢查
Price:
後面是否有空格字元。但如果加上
x
引數,所有空白都會忽略。如果想匹配一個空白實際字元,需要使用
\s
。
另外,如果加上了
x
選項,以
#
開頭的字元會被當成註釋,這類似於 PHP 中的
//
和
#
註釋語法。
我們可以在子模式兩邊新增空白,形成邏輯分組,這樣可以讓正則更易讀。但更好的辦法是將正則拆成多行,並逐行添加註釋:
- /Price: (?
在 PHP 中還可以使用 Heredoc 來保留字串格式。
$pattern = <<
注:
/ix
後不可以跟隨註釋資訊
字元組
正則表示式支援字元組。使用它們,可以不需要關注太多細節,從而提高可讀性。
\d
可能是最常用了字元組。
\d
表示匹配單個數字,在非Unicode模式下跟
[0-9]
是等價的。此外,
\D
表示
\d
的䃼集,跟
[^0-9]
等價。
如果我們想匹配文字中的單個數字(數字後面不是數字),我們可以使用字元組進行簡化:
- /Number: [0-9][^0-9]/+ /Number: \d\D/
正則表示式支援很多字元組,可以更好地簡化正則:
\w
跟
[A-Za-z0-9_]
等價:
- /[A-Za-z0-9_]/ + /\w/
[:xdigit:]
是命名字元組,可以匹配大小字字母和數字,跟
[A-Za-z0-9]
等價:
- /[a-zA-F0-9]/ + /[[:xdigit:]]/
\s
匹配所有空白字元,跟
[ \t\r\n\v\f]
等價:
- / \t\r\n\v\f/ + /\s/
如果使用
/u
選項,就開啟 Unicode 模式。Unicode 模式有更多的字元組。它的命名字元組都寫成
p{EXAMPLE}
,其中
EXAMPLE
就是字元組的名字。使用大寫的
P
(也就是
\P{FOO}
)來表示對應的補集。
例如,
\p{Sc}
表示所有在用和將來要用的貨幣符號。它還有一個更長的版本(即
\p{Currency_Symbol}
),但 PHP 現在在不支援。
$pattern = ’/Price: \p{Sc}\d+/u‘;$text = ’Price: ¥42‘;
使用字元組同樣可以用於匹配和捕獲分組。如果以後有了新的貨幣符號,只要相關資訊加入的 Unicode 資料庫,就能匹配到。
Unicode 字元組還包含很多語言相關的字元組。比如,
\p{Sinhala}
表示所有僧伽邏語文字,等價於
\x{0D80}-\x{0DFF}
。
- $pattern = ’/[\x{0D80}-\x{0DFF}]/u‘;+ $pattern = ’/\p{Sinhala}]/u‘;$text = ’පීඑච්පී。වොච්`;$contains_sinhala = preg_match($pattern, $text);
作者:墨娘
連結:
https://www。imoniang。com/73。html
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。