1 POSIX正則表達式
1.1 頭文件
#include
1.2 注意事項
不過(guò)在C++的字符串中,'\'表示轉義后面的字符,'\\'才表示一個(gè)'\',而在正則中,'\'也表示轉義,所以一個(gè)'正常'的正則在修改為C++正則時(shí),所有的'\'都要加倍。
POSIX正則表達式有自己的一套規范,與常用的正則表達式不完全相同。
1.3 基本方法
1.3.1 regcomp
函數原型
int regcomp(regex_t *preg, const char *regex, int cflags);
功能
編譯正則表達式,以便regexec方法使用。
參數含義
preg
preg是一個(gè)指向編譯后的正則表達式,p意思是pointer,reg意思是regex_t類(lèi)型。
regex_t是一個(gè)結構體數據類(lèi)型,用來(lái)存放編譯后的正則表達式,它的成員re_nsub用來(lái)存儲正則表達式中的子正則表達式的個(gè)數,子正則表達式就是用圓括號包起來(lái)的部分表達式。
regex
描述正則表達式的字符串。
cflags
決定編譯的類(lèi)型。
REG_EXTENDED
擴展的正則表達式,如果不設置則為基本的正則表達式。
REG_ICASE
不區分大小寫(xiě)。
REG_NOSUB
不用存儲匹配后的結果。
如果設置該標志位,那么在regexec將忽略nmatch和pmatch兩個(gè)參數。
REG_NEWLINE
識別換行符。
‘^’匹配行的開(kāi)頭, 不管regexec中是否設置eflags為REG_NOTBOL。
‘$’匹配行的末尾, 不管regexec中是否設置eflags為REG_NOTEOL。
例如:pattern=”^a$” 被檢測字符串為”\na”,那么設置REG_NEWLINE才能匹配成功。
返回值
成功返回0
失敗返回錯誤碼
1.3.2 regexec
函數原型
int regexec(const regex_t *preg, const char *string, size_t nmatch,
regmatch_t pmatch[], int eflags);
功能
根據編譯好的正則表達式,對字符串進(jìn)行匹配。
參數含義
preg
經(jīng)過(guò)regcomp編譯的正則表達式。
string
待匹配的字符串。
nmatch
匹配到的字符串的個(gè)數。
pmatch
匹配到的數組。
regmatch_t 的定義如下
typedef struct {
regoff_t rm_so;
regoff_t rm_eo;
} regmatch_t;
rm_so
如果-1==rm_so,表示沒(méi)有匹配到。
如果-1!=rm_so,表示string中下一個(gè)最大子字符串的偏移量(舉例string開(kāi)頭的偏移量)。
rm_eo
子字符串的長(cháng)度。
eflags
REG_NOTBOL
NOTBOL = not-begin-of-line
不匹配行的開(kāi)頭,除非在regcomp編譯時(shí)cflag設置REG_NEWLINE。
該標志位可能在如下情況使用,傳遞string的不同部分給regexec,并且string的開(kāi)頭不能被解釋成行的開(kāi)頭。
REG_NOTEOL
NOTEOL = not-end-of-line
不匹配行的結束,除非在regcomp編譯時(shí)cflag設置REG_NEWLINE。
返回值
成功返回0
失敗返回REG_NOTMATCH
1.3.3 regerror
函數原型
size_t regerror(int errcode, const regex_t *preg, char *errbuf,
size_t errbuf_size);
功能
將regcomp和regexec返回的errorcode轉換成錯誤信息。
參數含義
errorcode
錯誤碼,由regcomp或regexec獲得。
preg
經(jīng)過(guò)regcomp編譯的正則表達式。
errbuf
存儲錯誤信息的buffer。
errbuf_size
errbuf的大小。
返回值
返回errbuf存儲錯誤信息所需要的大小。
如果errbuf和errbuf_size都是非0,那么errbuf中有errbuf_size-1大小錯誤信息字符串,最后是字符串結束標記(‘\0’)。
錯誤碼
regcomp方法返回的錯誤碼
REG_BADBR
BR=back reference
非法使用返回引用操作符
REG_BADPAT
PAT=pattern
非法使用正則表達式操作符,例如group和list
REG_BADRPT
RPT=repetition
非法使用重復的操作符,例如在第一個(gè)字符使用 ’*’
REG_EBRACE
EBRACE=error brace
大括號不匹配
REG_EBRACK
EBRACK=error bracket
中括號不匹配
REG_ECOLLATE
ECOLLATE=error collate
非法參數
REG_ECTYPE
ECTYPE=error character type
錯誤的字符類(lèi)型
REG_EEND
EEND=error end
無(wú)具體錯誤。該錯誤碼在POSIX.2中沒(méi)有定義
REG_EESCAPE
EESCAPE=error escape
多余的\
REG_EPAREN
EPAREN=error parenthesis
圓括號不匹配
REG_ERANGE
ERANGE=error range
非法使用范圍操作符,例如結束的標識出現在開(kāi)始標識之前
REG_ESIZE
ESIZE=error size
編譯正則表達式需要的內存大于64kb,在POSIX.2中沒(méi)有定義
REG_ESPACE
ESPACE=error space
編譯正則表達式已經(jīng)超出內存空間
REG_ESUBREG
ESUBREG=error reference to a subexpression
非法使用子表達式的引用
1.3.4 regefree
函數原型
void regfree(regex_t *preg);
功能
釋放由regcomp編譯preg的內存。
參數含義
preg
經(jīng)過(guò)regcomp編譯的正則表達式。
返回值
void
1.4 POSIX正則表達式規范
參考:
http://en.wikipedia.org/wiki/Regular_expressionPOSIX正則表達式分為Basic Regular Expressions 和 Extended Regular Expressions。
ERE增加支持?,+和|,去除了通配符()和{}。而且POSIX正則表達式的標準語(yǔ)法經(jīng)常堅持使用附加的語(yǔ)法來(lái)支持特殊應用。雖然POSIX.2沒(méi)有實(shí)現一些具體的細節,BRE和ERE提供被很多工具使用的標準。
BRE要求通配符()和{}寫(xiě)成和\{\},ERE中無(wú)需這樣。
1.4.1 基本通配符
通配符
描述
.
匹配任何一個(gè)單字符(許多應用不包括換行符,但是假設包括換行符也是安全的)。在大中小括號中,該通配符只匹配字符’.’,例如,a.c匹配”abc”,但是[a.c]只匹配”a”或”.”或”c”。要匹配’.’不能使用’\.’,因為’\.’的作用與’.’一樣,應該使用’[.]’來(lái)匹配”a.c”中的點(diǎn)。
[]
只匹配中括號內的一個(gè)字符。例如[abc]匹配”a”或”b”或”c”。[a-z]匹配小寫(xiě)的”a”到”z”。這些格式可以混合使用:[abcx-z]匹配”a”,”b”,”c”,”x”,”y”,”z”與[a-cx-z]的效果相同。
‘-’如果出現在[]的開(kāi)頭或結尾,表示匹配字符’-’,例如[^-abc],[-abc] ,[abc-]。注意不能使用’\’。
‘]’可以出現在中括號中的第一個(gè)位置,例如[]abc]或[^]abc]
[^ ]
匹配單個(gè)字符,該字符不能包含在中括號中。例如,[^abc]匹配任何除’a’,’b’,’c’外的字符。[^a-z]匹配任何除’a’-‘z’的字符。同理,字符與范圍標識可以混合使用,例如,[^a-cx-z]。
^
匹配字符串的開(kāi)頭。在基于行的工具中,匹配每一行的開(kāi)頭。
$
匹配字符串的結尾或者換行符的前一個(gè)位置。在基于行的工具中,匹配每一行的結尾。
()
定義一個(gè)子表達式。圓括號在整體匹配完后進(jìn)行匹配。子表達式也叫做塊或組。
BRE模式需要寫(xiě)成
\n
匹配第n(1個(gè)子表達式。POSIX.2對該通配符的定義很模糊,有的工具允許引用大于9的子表達式。
*
匹配前一個(gè)元素0次或多次。例如,ab*c匹配”ac”,”abc”,”abbbbbc”等。[xyz]*匹配””,”x”,”y”,”z”,”zx”,”zyx”,”xyzzy”等。(ab)*匹配””,”ab”,”abab”等。
{m,n}
匹配前一個(gè)元素至少m次,至多n次。例如,a{3,5}匹配”aaa”,”aaaa”,”aaaaa”。
BRE模式需要寫(xiě)成\{m,n\}
舉例
.at
匹配任何以”at”結尾長(cháng)度為3的字符串,例如” at”,”aat”,”cat”等
[hc]at
匹配”hat”和”cat”
[^b]at
除了”bat”,匹配任何以”at”結尾長(cháng)度為3的字符串。例如,”aat”,”cat”等
[^hc]at
除了”hat”和”cat”,匹配任何以”at”結尾長(cháng)度為3的字符串。例如,”aat”,”tat”等
^[hc]at
匹配任何以”hat”或”cat”開(kāi)頭的字符串或行
[hc]at$
匹配任何以”hat”或”cat”結尾的字符串或行
.
匹配任何三個(gè)字符,第一個(gè)和第三個(gè)字符必須分別為”[”,”]”,因為”\[”和”\[”是經(jīng)過(guò)轉義,例如”[a]”,”[b]”
s.*
匹配任何以”s”開(kāi)頭的字符串,例如”swa”,”seed”
1.4.2 Extended Regular Expressions
在ERE中,反斜杠’\’用來(lái)對通配符進(jìn)行轉義,所以BRE中的’\(’和’\)’在ERE中改為’(’和’)’,’\{’和’\}’改為’{’和’}’。 ERE移除了’\n’通配符,并添加了如下通配符。
通配符
描述
?
匹配前一個(gè)元素0次或1次。例如,ab?c匹配”ac”,”abc”。
+
匹配前一個(gè)元素1次或多次。例如,ab+c匹配”abc”,”abbc”等,但是不能匹配”ac”。
|
匹配前一個(gè)表達式或后一個(gè)表達式。例如,abc|def匹配”abc”,”def”。
舉例
[hc]+at
匹配”hat”,”cat”,”hhat”,”ccat”等,但是不匹配”at”
[hc]?at
匹配”hat”,”cat”,”at”
[hc]*at
匹配”at”,”hat”,”cat”,”hcat”等
cat|dog
匹配”cat”,”dog”
1.4.3 Character classes
character class 是除了字面匹配最基本的正則表達式。它是很小的字符序列匹配更大的字符序列。例如,[A-Z]可以表示字母表,\d表示任意數字。character class應用于BRE和ERE。
當使用范圍通配符時(shí),例如[a-z]。計算機本地設置決定了字符編碼的順序。計算機可能按a-z的順序來(lái)存儲,或者abc…zABC…Z,或者aAbBcC…zZ的順序。所以POSIX定義了character class,正則表達式的處理器可以正確解析該character class。
POSIX
ASCII
描述
[:alnum:]
[A-Za-z0-9]
數字和字母字符
[:alpha:]
[A-Za-z]
字母字符
[:blank:]
[ \t]
空格和TAB
[:cntrl:]
[\x00-\x1F\x7F]
控制符
[:digit:]
[0-9]
數字
[:graph:]
[\x21-\x7E]
可視字符
[:lower:]
[a-z]
小寫(xiě)字母字符
[:print:]
[\x20-\x7E]
可視字符和空格
[:punct:]
[][!'#$%&'()*+,./:;?@\^_`{|}~-]
標點(diǎn)符號
[:space:]
[ \t\r\n\v\f]
空白字符
[:upper:]
[A-Z]
大寫(xiě)字母字符
[:xdigit:]
[A-Fa-f0-9]
十六進(jìn)制字符
POSIX定義的character class只能在中括號內使用。例如,[[:upper:]ab]匹配大寫(xiě)字母字符和”a”,”b”。
[:word:]是附加的非POSIX的character class,[:word:]表示[:alnum:]和下劃線(xiàn)。這表明在很多編程語(yǔ)言中,這些通配符可能是標識符。
1.5 源碼示例
我的github:
https://github.com/loverszhaokai/libutil/blob/master/src/regex_util.cclibutil庫中包含有regex的示例。歡迎批評指正,一起修改。
/******************************************************************************libutilAuthor: zhaokaiEmail: loverszhao@gmail.comReference: POSIXDescription:Version: 1.0******************************************************************************/#include 'util/regex_util.h'#include #include #include #include namespace util{boolRegexMatch(const std::string &_pattern, const std::string &_input, std::vectorstring> *_output, std::string *_err_msg){ _output->clear(); *_err_msg = ''; regex_t reg; int comp_result = regcomp(®, _pattern.c_str(), REG_EXTENDED | REG_NEWLINE); if (0 == comp_result) { // Add 1 in case of '' == _input size_t nmatch = 1 + std::min(_input.length(), UINT_MAX - 1); regmatch_t pmatch[nmatch]; int exec_result = regexec(®, _input.c_str(), nmatch, pmatch, 0); if (0 == exec_result) { for (size_t i = 0; i i) { if (-1 == pmatch[i].rm_so) break; _output->push_back(_input.substr(pmatch[i].rm_so, pmatch[i].rm_eo)); } } } else { const size_t errbuf_size = 1000; char errbuf[errbuf_size + 1]; regerror(comp_result, ®, errbuf, errbuf_size); *_err_msg = errbuf; regfree(®); return false; } regfree(®); return true;}boolFullMatch(const std::string &_pattern, const std::string &_input){ std::vectorstring> output; std::string err_msg = ''; bool result = RegexMatch(_pattern, _input, &output, &err_msg); if (!result) { std::cout std::endl; return false; } else { // full-match in case of @_input == output[0] if (0 0]) return true; } return false;}}; // namespace util
單元測試文件,包含了Mac,IP,Port,文件夾路徑的正則表達式測試
/******************************************************************************libutilAuthor: zhaokaiEmail: loverszhao@gmail.comReference: chromiumDescription:Version: 1.0******************************************************************************/#include 'util/regex_util.h'#include 'util/basictypes.h'#include 'third_party/gtest/include/gtest/gtest.h'namespace util{// ------------------------------------------------------------// --------------------- Update Begin -------------------------// ------------------------------------------------------------TEST(RegexUtilTest, RegexMatch){ struct { const std::string pattern; const std::string input; const std::vectorstring> expect_output; bool result; } cases [] = { { 'c', 'c', {'c'}, true}, { 'c', '', {}, true}, { '(', '', {}, false}, { '0*', '', {''}, true}, { '0*', '0', {'0'}, true}, { '0*', '0000', {'0000'}, true}, }; for (size_t i = 0; i i) { std::string err_msg = ''; std::vectorstring> output; EXPECT_EQ(cases[i].result, RegexMatch(cases[i].pattern, cases[i].input, &output, &err_msg)) 'cases[' ']'; EXPECT_EQ(cases[i].expect_output, output) 'cases[' ']'; }}TEST(RegexUtilTest, FullMatch){ struct { const std::string pattern; const std::string input; bool result; } cases [] = { { 'c', 'c', true}, { 'c', '', false}, { '(', '', false}, { '^[0-9]*$', '12345', true}, { '^[0-9]*$', '00000', true}, { '^[0-9]*$', '1', true}, { '\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*', 'loverszhao@gmail.com', true}, { '[0-9]{5}', '00000', true}, { '([0-9]){5}', '00000', true}, { '(([0-9]){5}){2}', '0000000000', true}, { '(([0-9]){5}){2}', '0000011111', true}, { '(([0-9]){5}){2}', '0123456789', true}, { '[0-9]', '0', true}, { '0*', '00000', true}, { '0*', '', true}, { '0?', '', true}, { '0?', '0', true}, { '0?', '00', false}, { '[a-z][0-9]{2}{3}', 'x000000', true}, { '^a$', 'a', true}, { '^a$', '\na', false}, { '.', 'a', true}, { '.', 'abc', false}, { '\.', '.', true}, { '[.]', 'a', false}, { '[:digit:]', '1', false}, { '[[:digit:]]', '1', true}, { '[[:digit:]]{1,2}', '1', true}, { '[[:digit:]]{1,2}', '99', true}, { '^[[:digit:]]{1,2}', '1', true}, { '^[[:digit:]]{1,2}', '99', true}, //'^([[:digit:]]{1,2}|1[[:digit:]][[:digit:]]|2[0-4][[:digit:]]|25[0-5])\.'; { '^([[:digit:]]{1,2}|1[[:digit:]][[:digit:]])', '99', true}, { '^([[:digit:]]{1,2}|1[[:digit:]][[:digit:]])', '199', true}, { '^([[:digit:]]{1,2}|1[[:digit:]][[:digit:]]|2[0-4][[:digit:]]|25[0-5])', '99', true}, { '^([[:digit:]]{1,2}|1[[:digit:]][[:digit:]]|2[0-4][[:digit:]]|25[0-5])', '199', true}, { '^([[:digit:]]{1,2}|1[[:digit:]][[:digit:]]|2[0-4][[:digit:]]|25[0-5])', '239', true}, { '^([[:digit:]]{1,2}|1[[:digit:]][[:digit:]]|2[0-4][[:digit:]]|25[0-5])', '250', true}, { '^([[:digit:]]{1,2}|1[[:digit:]][[:digit:]]|2[0-4][[:digit:]]|25[0-5])\.', '250.', true}, { '^([\\/]?[[:alnum:]_]+)*$', 'file_name', true}, }; for (size_t i = 0; i i) { EXPECT_EQ(cases[i].result, FullMatch(cases[i].pattern, cases[i].input)) 'cases[' '] pattern=' cases[i].pattern ' input=' std::endl; }}namespace{// Return true if @_mac is valid, such as '11:11:11:11:11:11'// or '11 11 11 11 11 11' or '111111111111'// flase otherwiseboolValidateMac(const std::string &_mac){ const std::string pattern = '^[0-9a-fA-F]{2}([ -:][0-9a-fA-F]{2}){5}'; return FullMatch(pattern, _mac);} // Return true if @_ip is valid, such as '192.168.4.1'// flase otherwiseboolValidateIP(const std::string &_ip){ const std::string pattern = '^([[:digit:]]{1,2}|1[[:digit:]][[:digit:]]|2[0-4][[:digit:]]|25[0-5])[.]' '([[:digit:]]{1,2}|1[[:digit:]][[:digit:]]|2[0-4][[:digit:]]|25[0-5])[.]' '([[:digit:]]{1,2}|1[[:digit:]][[:digit:]]|2[0-4][[:digit:]]|25[0-5])[.]' '([[:digit:]]{1,2}|1[[:digit:]][[:digit:]]|2[0-4][[:digit:]]|25[0-5])$'; return FullMatch(pattern, _ip);}// Return true if @_port is valid which should between '0~65535'// and it can be '0' or '65535'// false otherwiseboolValidatePort(const std::string &_port){ const std::string pattern = '^([1-9]|[1-9][[:digit:]]{1,3}|[1-6][0-5][0-5][0-3][0-5])$'; return FullMatch(pattern, _port);}// Return true if @_path is valid, such as '/1/2/3' or '1/2/3/'// false otherwiseboolValidatePath(const std::string &_path){ //const std::string pattern = '^[a-zA-Z]:(((//(?! )[^///:*?\'|]+)+//?)|(//))[:space:]*$'; //const std::string pattern = '(^[.]|^/|^[a-zA-Z])?:?/[.]+(/$)? '; //const std::string pattern = '(\/([0-9a-zA-Z]+))+'; const std::string pattern = '^([\\/]?[[:alnum:]_]+)*$'; return FullMatch(pattern, _path);}TEST(RegexUtilTest, ValidateMac){ struct { const std::string input; bool result; } cases [] = { { ':11:22:33:44:55:66', false}, { '11:22:33:44:55:66', true}, { '11-22-33-44-55-66', true}, { '11 22 33 44 55 66', true}, { '112233445566', false}, { 'c', false}, { 'c', false}, { '(', false}, }; for (size_t i = 0; i i) { EXPECT_EQ(cases[i].result, ValidateMac(cases[i].input)) 'cases[' '] input=' std::endl; }}TEST(RegexUtilTest, ValidateIP){ struct { const std::string input; bool result; } cases [] = { // Normal IP { '192.168.4.1', true}, { '10.0.0.1', true}, { '192.168.4.244', true}, // Unnormal IP { '292.168.4.244', false}, { '192.268.-1.244', false}, { '192.168.4.264', false}, { '....', false}, { '192.168.1.', false}, { '192 168 1 1', false}, }; for (size_t i = 0; i i) { EXPECT_EQ(cases[i].result, ValidateIP(cases[i].input)) 'cases[' '] input=' std::endl; }}TEST(RegexUtilTest, ValidatePort){ struct { const std::string input; bool result; } cases [] = { // Normal Port { '1', true}, { '99', true}, { '199', true}, { '200', true}, { '999', true}, { '1999', true}, { '30000', true}, { '30001', true}, { '60000', true}, { '65535', true}, // Unnormal Port { '0', false}, { '-1', false}, { '00', false}, { '010', false}, { '65536', false}, { '65537', false}, { '111111', false}, }; for (size_t i = 0; i i) { EXPECT_EQ(cases[i].result, ValidatePort(cases[i].input)) 'cases[' '] input=' std::endl; }}TEST(RegexUtilTest, ValidatePath){ struct { const std::string input; bool result; } cases [] = { // Normal Path { '1', true}, { '/1/2/3', true}, { '1/2/3', true}, // Unnormal Path { '.', false}, { '..', false}, { '/1/./', false}, { '/1/../', false}, { '//1/2', false}, { '/1/...', false}, { '/1//', false}, { './1/./', false}, { './1/../', false}, { './1/2/3', false}, { './1/2/3/', false}, { '1/./', false}, { '1/../', false}, }; for (size_t i = 0; i i) { EXPECT_EQ(cases[i].result, ValidatePath(cases[i].input)) 'cases[' '] input=' std::endl; }}}}; // namespace util