Linux/Unix工具與正則表達式的POSIX規範
對正則表達式有基本瞭解的讀者,必定不會陌生『\d』、『[a-z]+』之類的表達式,前者匹配一個數字字符,後者匹配一個以上的小寫英文字母。可是若是你用過vi、grep、awk、sed之類Linux/Unix下的工具或許會發現,這些工具雖然支持正則表達式,語法卻很不同,照一般習慣的辦法寫的『\d』、『[a-z]+』之類的正則表達式,每每不是沒法識別就是匹配錯誤。並且,這些工具自身之間也存在差別,一樣的結構,有時須要轉義有時不須要轉義。這,到底是爲何呢?
緣由在於,Unix/Linux下的工具大多采用POSIX規範,同時,POSIX規範又可分爲兩種流派(flavor)。因此,首先有必要了解一下POSIX規範。git
POSIX規範
常見的正則表達式記法,其實都源於Perl,實際上,正則表達式從Perl衍生出一個顯赫的流派,叫作PCRE(Perl Compatible Regular Expression),『\d』、『\w』、『\s』之類的記法,就是這個流派的特徵。可是在PCRE以外,正則表達式還有其它流派,好比下面要介紹的POSIX規範的正則表達式。(相似PHP中正則函數也分爲PCRE和POSIX兩個系列)
POSIX的全稱是Portable Operating System Interface for uniX,它由一系列規範構成,定義了UNIX操做系統應當支持的功能,因此「POSIX規範的正則表達式」其實只是「關於正則表達式的POSIX規範」,POSIX定義了BRE(Basic Regular Expression,基本型正則表達式)和ERE(Extended Regular Express,擴展型正則表達式)兩大流派。在兼容POSIX的UNIX系統上,grep和egrep之類的工具都遵循POSIX規範,一些數據庫系統中的正則表達式也符合POSIX規範。正則表達式
BRE
在Linux/Unix經常使用工具中,grep、vi、sed都屬於BRE這一派,它的語法看起來比較奇怪,元字符『(』、『)』、『{』、『}』必須轉義以後才具備特殊含義,因此正則表達式『(a)b』只能匹配字符串 (a)b而不是字符串ab;正則表達式『a{1,2}』只能匹配字符串a{1,2},正則表達式『a\{1,2\}』才能匹配字符串a或者aa。
之因此這麼麻煩,是由於這些工具的誕生時間很早,正則表達式的許多功能倒是逐步發展演化出來的,以前這些元字符可能並無特殊的含義;爲保證向後兼容,就只能使用轉義。並且有些功能甚至根本就不支持,好比BRE就不支持『+』和『?』量詞,也不支持多選結構『(…|…)』和反向引用『\1』、『\2』…。
不過今天,純粹的BRE已經不多見了,畢竟你們已經認爲正則表達式「理所應當」支持多選結構和反向引用等功能,沒有確實太不方便。因此雖然vi屬於BRE流派,但提供了這些功能。GNU也對BRE作了擴展,支持『+』、『?』、『|』,只是使用時必須寫成『\+』、『\?』、『\|』,並且也支持『\1』、『\2』之類反向引用。這樣,GNU的grep等工具雖然名義上屬於BRE流,但更確切的名稱是GNU BRE。數據庫
ERE
在Linux/Unix經常使用工具中,egrep、awk則屬於ERE這一派,。雖然BRE名爲「基本」而ERE名爲「擴展」,但ERE並不要求兼容BRE的語法,而是自成一體。所以其中的元字符不用轉義(在元字符以前添加反斜線會取消其特殊含義),因此『(ab|cd)』就能夠匹配字符串ab或者cd,量詞『+』、『?』、『{n,m}』能夠直接使用。ERE並無明確規定支持反向引用,可是很多工具都支持『\1』、『\2』之類的反向引用。
GNU出品的egrep等工具就屬於ERE流(更準確的名字是GNU ERE),但由於GNU已經對BRE作了很多擴展,所謂的GNU ERE其實只是個說法而已,它有的功能GNU BRE都有了,只是元字符不須要轉義而已。
下面的表格簡要說明了幾種POSIX流派的區別(其實,如今的BRE和ERE在功能上並無什麼區別,主要的差別是在元字符的轉義上)。
幾種POSIX流派的說明(重要)express
流派編程 |
說明vim |
工具編程語言 |
BRE函數 |
(、)、{、}都必須轉義使用,不支持+、?、|工具 |
grep、sed、vi(但vi支持這些多選結構和反向引用)編碼 |
GNU BRE |
(、)、{、}、+、?、|都必須轉義使用 |
GNU grep、GNU sed |
ERE |
元字符沒必要轉義,+、?、(、)、{、}、|能夠直接使用,\1、\2的支持不肯定 |
egrep、awk |
GNU ERE |
元字符沒必要轉義,+、?、(、)、{、}、|能夠直接使用,支持\1、\2 |
grep –E、GNU awk |
爲了方便查閱,下面再用一張表格列出基本的正則功能在經常使用工具中的表示法,其中的工具GNU的版本爲準。
經常使用Linux/Unix工具中的表示法
PCRE記法 |
vi/vim |
grep |
awk |
sed |
* |
* |
* |
* |
* |
+ |
\+ |
\+ |
+ |
\+ |
? |
\= |
\? |
? |
\? |
{m,n} |
\{m,n} |
\{m,n\} |
{m,n} |
\{m,n\} |
\b * |
\< \> |
\< \> |
\< \> |
\y \< \> |
(…|…) |
\(…\|…\) |
\(…\|…\) |
(…|…) |
(…|…) |
(…) |
\(…\) |
\(…\) |
(…) |
(…) |
\1 \2 |
\1 \2 |
\1 \2 |
不支持 |
\1 \2 |
注:PCRE中經常使用\b來表示「單詞的起始或結束位置」,但Linux/Unix的工具中,一般用\<來匹配「單詞的起始位置」,用\>來匹配「單詞的結束位置」,sed中的\y能夠同時匹配這兩個位置。
POSIX字符組
在某些文檔中,你還會發現相似『[:digit:]』、『[:lower:]』之類的表示法,它們看起來不難理解(digit就是「數字」,lower就是「小寫」),但又很奇怪,這就是POSIX字符組。不只在Linux/Unix的常見工具中,甚至一些變成語言中都出現了這些字符組,爲避免困惑,這裏有必要簡要介紹它們。
在POSIX規範中,『[a-z]』、『[aeiou]』之類的記法仍然是合法的,其意義與PCRE中的字符組也沒有區別,只是這類記法的準確名稱是POSIX方括號表達式(bracket expression),它主要用在Unix/Linux系統中。POSIX方括號表示法與PCRE字符組的最主要差異在於:POSIX字符組中,反斜線\不是用來轉義的。因此POSIX方括號表示法『[\d]』只能匹配\和d兩個字符,而不是『[0-9]』對應的數字字符。
爲了解決字符組中特殊意義字符的轉義問題,POSIX方括號表示法規定,若是要在字符組中表達字符](而不是做爲字符組的結束標記),應當讓它緊跟在字符組的開方括號以後,因此POSIX中,正則表達式『[]a]』能匹配的字符就是]和a;若是要在POSIX方括號表示法中表達字符-(而不是範圍表示法),必須將它緊挨在閉方括號]以前,因此『[a-]』能匹配的字符就是a和-。
POSIX規範也定義了POSIX字符組,它近似等價于于PCRE的字符組簡記法,用一個有直觀意義的名字來表示某一組字符,好比digit表示「數字字符」,alpha表示「字母字符」。
不過,POSIX中還有一個值得注意的概念:locale(一般翻譯爲「語言環境」)。它是一組與語言和文化相關的設定,包括日期格式、貨幣幣值、字符編碼等等。POSIX字符組的意義會根據locale的變化而變化,下面的表格介紹了常見的POSIX字符組在ASCII語言環境與Unicode語言環境下的意義,供你們參考。
POSIX字符組 |
說明 |
ASCII語言環境 |
Unicode語言環境 |
[:alnum:]* |
字母字符和數字字符 |
[a-zA-Z0-9] |
[\p{L&}\p{Nd}] |
[:alpha:] |
字母 |
[a-zA-Z] |
\p{L&} |
[:ascii:] |
ASCII字符 |
[\x00-\x7F] |
\p{InBasicLatin} |
[:blank:] |
空格字符和製表符 |
[ \t] |
[\p{Zs}\t] |
[:cntrl:] |
控制字符 |
[\x00-\x1F\x7F] |
\p{Cc} |
[:digit:] |
數字字符 |
[0-9] |
\p{Nd} |
[:graph:] |
空白字符以外的字符 |
[\x21-\x7E] |
[^\p{Z}\p{C}] |
[:lower:] |
小寫字母字符 |
[a-z] |
\p{Ll} |
[:print:] |
相似[:graph:],但包括空白字符 |
[\x20-\x7E] |
\P{C} |
[:punct:] |
標點符號 |
[][!"#$%&'()*+,./:;<=>?@\^_`{|}~-] |
[\p{P}\p{S}] |
[:space:] |
空白字符 |
[ \t\r\n\v\f] |
[\p{Z}\t\r\n\v\f] |
[:upper:] |
大寫字母字符 |
[A-Z] |
\p{Lu} |
[:word:]* |
字母字符 |
[A-Za-z0-9_] |
[\p{L}\p{N}\p{Pc}] |
[:xdigit:] |
十六進制字符 |
[A-Fa-f0-9] |
[A-Fa-f0-9] |
注1:標記*的字符組簡記法並非POSIX規範中的,但使用不少,通常語言中都提供,文檔中也會出現。
注2:對應的Unicode屬性請參考本系列文章已經刊發過的關於Unicode的部分。
POSIX字符組的使用有所不一樣。主要區別在於,PCRE字符組簡記法能夠脫離方括號直接出現,而POSIX字符組必須出如今方括號內,因此一樣是匹配數字字符,單獨出現時,PCRE中能夠直接寫『\d』,而POSIX字符組就必須寫成『[[:digit:]]』。
Linux/Unix下的工具中,通常均可以直接使用POSIX字符組,而PCRE的字符組簡記法『\w』、『\d』等則大多不支持,因此若是你看到『[[:space:]]』而不是『\s』,必定不要感到奇怪。 不過,在經常使用的編程語言中,Java、PHP、Ruby也支持使用POSIX字符組。其中Java和PHP中的POSIX字符組都是按照ASCII語言環境進行匹配;Ruby的狀況則要複雜一點,Ruby 1.8按照ASCII語言環境進行匹配,並且不支持『[:word:]』和『[:alnum:]』,Ruby 1.9按照Unicode語言環境進行匹配,同時支持『[:word:]』和『[:alnum:]』。