"換行符號" 的定義不明確
-- Windows 使用 CR+LF (\r\n)
-- Linux 使用 LF (\n)
-- OSX 使用 CR (\r)
鮮為人知的特殊字元
\R 在 preg_* 中匹配所有三種情況。
preg_match( '/^\R$/', "match\nany\\n\rline\r\nending\r" ); // 匹配任何換行符號
反斜線字元有幾種用途。首先,如果它後面跟著一個非字母數字字元,它會移除該字元的任何特殊含義。這種將反斜線作為跳脫字元的使用方式適用於字元類別的內外。
例如,如果您想要匹配 "*" 字元,您可以在樣式中寫入 "\*"。無論後面的字元是否會被解譯為元字元,這都適用,因此總是安全地在非字母數字字元前面加上 "\" 以指定它代表自身。特別是,如果您想要匹配反斜線,您可以寫入 "\\\\ "。
注意:
單引號和雙引號 PHP 字串對於反斜線有特殊含義。因此,如果 \ 必須使用正規表示式 \\ 來匹配,則必須在 PHP 程式碼中使用 "\\\\" 或 '\\\\'。
如果樣式以 PCRE_EXTENDED 選項編譯,則樣式中的空白字元(字元類別中除外)以及字元類別外 "#" 與下一個換行字元之間的字元會被忽略。可以使用跳脫反斜線將空白字元或 "#" 字元包含為樣式的一部分。
反斜線的第二種用途是提供一種以可見方式在樣式中編碼非列印字元的方法。非列印字元的外觀沒有限制,除了終止樣式的二進位零之外,但是當透過文字編輯來準備樣式時,通常使用以下跳脫序列之一會比它代表的二進位字元更容易。
"\cx
" 的精確效果如下:如果 "x
" 是小寫字母,則會將其轉換為大寫字母。然後反轉字元的第 6 位 (十六進位 40)。因此 "\cz
" 變成十六進位 1A,但 "\c{
" 變成十六進位 3B,而 "\c;
" 變成十六進位 7B。
在 "\x
" 之後,會讀取最多兩個十六進位數字(字母可以使用大寫或小寫)。在UTF-8 模式中,允許使用 "\x{...}
",其中大括號的內容是十六進位數字字串。它會被解譯為 UTF-8 字元,其碼位是給定的十六進位數字。如果值大於 127,則原始十六進位跳脫序列 \xhh
會匹配一個兩位元組 UTF-8 字元。
在 "\0
" 之後,會讀取最多兩個額外的八進位數字。在這兩種情況下,如果少於兩個數字,則只會使用現有的數字。因此,序列 "\0\x\07
" 指定兩個二進位零,後面跟著一個 BEL 字元。如果後面的字元本身是八進位數字,請確保在初始零之後提供兩個數字。
處理反斜線後面跟著非 0 數字的情況很複雜。在字元類別之外,PCRE 會讀取它以及任何後續數字,作為十進位數字。如果該數字小於 10,或者如果該運算式中至少有那麼多個先前的左括號,則整個序列會被視為反向參考。稍後在討論加上括號的子樣式後,會描述其工作方式。
在字元類別內部,或者如果十進位數字大於 9 且沒有那麼多個擷取的子樣式,則 PCRE 會重新讀取反斜線後最多三個八進位數字,並從該值的最低有效 8 位產生一個位元組。任何後續數字都代表自己。例如
請注意,100 或更大的八進位值不得以開頭為零,因為永遠不會讀取超過三個八進位數字。
定義單一位元組值的所有序列都可以在字元類別的內外使用。此外,在字元類別內部,序列 "\b
" 會被解譯為退格字元 (十六進位 08)。在字元類別之外,它有不同的含義(請參閱下文)。
反斜線的第三種用途是用於指定泛型字元類型
每一對跳脫序列都將完整的字元集劃分為兩個不相交的集合。任何給定的字元都會匹配每一對中的一個,而且只有一個。
「空白」字元是 HT (9)、LF (10)、FF (12)、CR (13) 和空格 (32)。但是,如果正在進行特定於地區的匹配,則碼位範圍在 128-255 的字元也可能被視為空白字元,例如 NBSP (A0)。
「單字」字元是任何字母或數字或底線字元,也就是任何可以成為 Perl「單字」一部分的字元。字母和數字的定義由 PCRE 的字元表控制,如果正在進行特定於地區的匹配,則可能會有所不同。例如,在「fr」(法文)地區中,某些大於 128 的字元碼用於帶重音的字母,這些字母與 \w
相匹配。
這些字元類型序列可以出現在字元類別的內外。它們各自匹配一個適當類型的字元。如果目前的匹配點位於主體字串的末尾,則所有這些都會失敗,因為沒有要匹配的字元。
反斜線的第四種用途是用於某些簡單的斷言。斷言指定必須在匹配中的特定點滿足的條件,而不會從主體字串中消耗任何字元。下面描述子樣式在更複雜斷言中的使用。反斜線斷言如下
這些斷言不能出現在字元類別中(但請注意,在字元類別內 "\b
" 有不同的含義,即退格字元)。
一個單字邊界是指在主旨字串中,目前字元和前一個字元不同時匹配 \w
或 \W
的位置(即一個匹配 \w
而另一個匹配 \W
),或者如果第一個或最後一個字元分別匹配 \w
,則為字串的開頭或結尾。
\A
、\Z
和 \z
斷言與傳統的抑揚符號和美元符號(在錨點中描述)的不同之處在於,無論設定什麼選項,它們都只匹配主旨字串的最開頭和結尾。它們不受 PCRE_MULTILINE 或 PCRE_DOLLAR_ENDONLY 選項的影響。\Z
和 \z
之間的區別在於,\Z
會匹配字串最後一個字元是換行符之前的位置,以及字串結尾的位置,而 \z
僅匹配結尾。
只有在目前匹配位置是匹配的起點時,\G
斷言才會成立,此起點由 preg_match() 的 offset
參數指定。當 offset
的值為非零時,它與 \A
不同。
\Q
和 \E
可用於忽略模式中的正規表示式元字元。例如:\w+\Q.$.\E$
將匹配一個或多個單字字元,後面接著文字 .$.
並錨定在字串的結尾。請注意,這不會變更分隔符號的行為;例如,模式 #\Q#\E#$
是無效的,因為第二個 #
標記了模式的結尾,而 \E#
被解讀為無效的修飾符號。
\K
可用於重置匹配起點。例如,模式 foo\Kbar
匹配 "foobar",但報告它已匹配 "bar"。使用 \K
不會干擾捕獲子字串的設定。例如,當模式 (foo)\Kbar
匹配 "foobar" 時,第一個子字串仍然設定為 "foo"。
"換行符號" 的定義不明確
-- Windows 使用 CR+LF (\r\n)
-- Linux 使用 LF (\n)
-- OSX 使用 CR (\r)
鮮為人知的特殊字元
\R 在 preg_* 中匹配所有三種情況。
preg_match( '/^\R$/', "match\nany\\n\rline\r\nending\r" ); // 匹配任何換行符號
大幅更新的版本(使用正確利用 \R 的新 $pat4,及其結果和註解)
請注意,像 \r、\R 和 \v 這樣的跳脫序列在意義和應用方面存在一些(有時難以一眼掌握)的細微差異 - 它們在所有情況下都不是完美的,但它們仍然非常有用。一些官方 PCRE 控制選項及其變更也很有幫助 - 遺憾的是,目前 php.net 上沒有記錄 (*ANYCRLF)、(*ANY) 或 (*CRLF)(儘管它們似乎已經可用超過 10 年 5 個月了),但它們在 Wikipedia(在 https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions 的「換行符號/斷行符號選項」中)和官方 PCRE 程式庫網站(在 http://www.pcre.org/original/doc/html/pcresyntax.html#SEC17 的「換行符號慣例」中)中有很好的描述。\R 的功能在不正確使用時,根據 php.net 以及官方說明(在 https://www.pcre.org/original/doc/html/pcrepattern.html#newlineseq 的「換行符號序列」中)來看,似乎有些令人失望(使用編譯時選項的預設設定)。
給那些試圖在多行模式 (/m) 下,正確匹配任何行尾 ($) 的模式問題的人一個提示(或至少解決這個問題)。
<?php
// 各種作業系統有不同的行尾(也稱為斷行符號)字元:
// - Windows 使用 CR+LF (\r\n);
// - Linux 使用 LF (\n);
// - OSX 使用 CR (\r)。
// 這就是為什麼單個美元符號元斷言 ($) 有時在多行修飾符 (/m) 模式下會失敗的原因 - 可能是 PHP 5.3.8 中的錯誤或只是「功能」 (?)。
$str="ABC ABC\n\n123 123\r\ndef def\rnop nop\r\n890 890\nQRS QRS\r\r~-_ ~-_";
// C 3 p 0 _
$pat1='/\w$/mi'; // 這在 JavaScript (Firefox 7.0.1+) 中效果很好
$pat2='/\w\r?$/mi'; // 稍微好一點
$pat3='/\w\R?$/mi'; // 根據 php.net 和 pcre.org 的說法,在不正確使用時,會有些令人失望
$pat4='/\w(?=\R)/i'; // 在允許先行斷言(僅偵測而不擷取)的情況下,在不使用多行 (/m) 模式時會好很多;請注意,若使用字串結尾的替代方案 ((?=\R|$)),它會如預期般抓取所有 7 個元素
$pat5='/\w\v?$/mi';
$pat6='/(*ANYCRLF)\w$/mi'; // 效果很好,但目前在 php.net 上沒有記錄(在 pcre.org 和 en.wikipedia.org 上有描述)
$n=preg_match_all($pat1, $str, $m1);
$o=preg_match_all($pat2, $str, $m2);
$p=preg_match_all($pat3, $str, $m3);
$r=preg_match_all($pat4, $str, $m4);
$s=preg_match_all($pat5, $str, $m5);
$t=preg_match_all($pat6, $str, $m6);
echo $str."\n1 !!! $pat1 ($n): ".print_r($m1[0], true)
."\n2 !!! $pat2 ($o): ".print_r($m2[0], true)
."\n3 !!! $pat3 ($p): ".print_r($m3[0], true)
."\n4 !!! $pat4 ($r): ".print_r($m4[0], true)
."\n5 !!! $pat5 ($s): ".print_r($m5[0], true)
."\n6 !!! $pat6 ($t): ".print_r($m6[0], true);
// 請注意 $pat2 (\r)、$pat3 和 $pat4 (\R)、$pat5 (\v) 以及 $pat6 ((*ANYCRLF)) 中更改的換行符號選項,這三個非常有用的跳脫序列之間的差異 - 至少對於某些應用程式而言是這樣。
/* 上述程式碼會產生以下輸出:
ABC ABC
123 123
def def
nop nop
890 890
QRS QRS
~-_ ~-_
1 !!! /\w$/mi (3): Array
(
[0] => C
[1] => 0
[2] => _
)
2 !!! /\w\r?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
3 !!! /\w\R?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
4 !!! /\w(?=\R)/i (6): Array
(
[0] => C
[1] => 3
[2] => f
[3] => p
[4] => 0
[5] => S
)
5 !!! /\w\v?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
6 !!! /(*ANYCRLF)\w$/mi (7): Array
(
[0] => C
[1] => 3
[2] => f
[3] => p
[4] => 0
[5] => S
[6] => _
)
*/
?>
遺憾的是,我無法存取執行最新 PHP 版本的伺服器 - 我本機的 PHP 版本是 5.3.8,而我的公用主機的 PHP 版本是 5.2.17。
由於 \v 可以匹配單字元換行符號 (CR、LF) 和雙字元換行符號 (CR+LF、LF+CR),它不是固定長度的原子 (例如,不允許在 lookbehind 斷言中使用)。
請注意,像 \r、\R 和 \v 這樣的跳脫序列在含義和應用上存在(有時乍看之下難以理解的)細微差別 - 它們在所有情況下都不是完美的,但它們仍然非常有用。一些官方 PCRE 控制選項及其變更也派得上用場 - 不幸的是,目前 php.net 上沒有關於 (*ANYCRLF)、(*ANY) 或 (*CRLF) 的文件(儘管它們似乎已經可用超過 10 年 5 個月了),但在維基百科(在 https://en.wikipedia.org/wiki/Perl_Compatible_Regular_Expressions 的「換行/斷行選項」)和官方 PCRE 函式庫網站(在 http://www.pcre.org/original/doc/html/pcresyntax.html#SEC17 的「換行慣例」)中有相當詳細的描述。\R 的功能(在編譯時選項的預設配置下)根據 php.net 以及官方描述(在 https://www.pcre.org/original/doc/html/pcrepattern.html#newlineseq 的「換行序列」)看來有些令人失望。
給那些試圖在多行模式 (/m) 下,正確匹配任何行尾 ($) 的模式問題的人一個提示(或至少解決這個問題)。
<?php
// 各種作業系統有不同的行尾(又稱換行符號):
// - Windows 使用 CR+LF (\r\n);
// - Linux 使用 LF (\n);
// - OSX 使用 CR (\r)。
// 這就是為什麼單美元元字符號 ($) 有時在多行修飾符 (/m) 模式下會失敗 - 可能是 PHP 5.3.8 中的錯誤或只是「功能」 (?)。
$str="ABC ABC\n\n123 123\r\ndef def\rnop nop\r\n890 890\nQRS QRS\r\r~-_ ~-_";
// C 3 p 0 _
$pat1='/\w$/mi'; // 這在 JavaScript (Firefox 7.0.1+) 中效果極佳
$pat2='/\w\r?$/mi';
$pat3='/\w\R?$/mi'; // 根據 php.net 和 pcre.org 的說法,這有些令人失望
$pat4='/\w\v?$/mi';
$pat5='/(*ANYCRLF)\w$/mi'; // 目前在 php.net 上未記載,但效果極佳
$n=preg_match_all($pat1, $str, $m1);
$o=preg_match_all($pat2, $str, $m2);
$p=preg_match_all($pat3, $str, $m3);
$r=preg_match_all($pat4, $str, $m4);
$s=preg_match_all($pat5, $str, $m5);
echo $str."\n1 !!! $pat1 ($n): ".print_r($m1[0], true)
."\n2 !!! $pat2 ($o): ".print_r($m2[0], true)
."\n3 !!! $pat3 ($p): ".print_r($m3[0], true)
."\n4 !!! $pat4 ($r): ".print_r($m4[0], true)
."\n5 !!! $pat5 ($s): ".print_r($m5[0], true);
// 請注意 $pat2 (\r)、$pat3 (\R)、$pat4 (\v) 中三個非常有用的跳脫序列之間的差異,以及 $pat5 中更改的換行選項 ((*ANYCRLF)) - 至少對於某些應用程式而言。
/* 上述程式碼會產生以下輸出:
ABC ABC
123 123
def def
nop nop
890 890
QRS QRS
~-_ ~-_
1 !!! /\w$/mi (3): Array
(
[0] => C
[1] => 0
[2] => _
)
2 !!! /\w\r?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
3 !!! /\w\R?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
4 !!! /\w\v?$/mi (5): Array
(
[0] => C
[1] => 3
[2] => p
[3] => 0
[4] => _
)
5 !!! /(*ANYCRLF)\w$/mi (7): Array
(
[0] => C
[1] => 3
[2] => f
[3] => p
[4] => 0
[5] => S
[6] => _
)
*/
?>
不幸的是,我無法存取具有最新 PHP 版本的伺服器 - 我的本地 PHP 版本是 5.3.8,而我的公共主機的 PHP 版本是 5.2.17。