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:]』。