目錄
-
1.1 頭文件
-
1.2 注意事項
-
1.3 基本方法
-
1.4 POSIX正則表達式基本規范
-
1.5 源碼示例
1 POSIX正則表達式
1.1 頭文件
#include <regex.h>
1.2 注意事項
不過在C++的字符串中,'\'表示轉義后面的字符,'\\'才表示一個'\',而在正則中,'\'也表示轉義,所以一個'正常'的正則在修改為C++正則時,所有的'\'都要加倍。
POSIX正則表達式有自己的一套規范,與常用的正則表達式不完全相同。
1.3 基本方法
1.3.1 regcomp
函數原型
int regcomp(regex_t *preg, const char *regex, int cflags);
功能
編譯正則表達式,以便regexec方法使用。
參數含義
preg
preg是一個指向編譯后的正則表達式,p意思是pointer,reg意思是regex_t類型。
regex_t是一個結構體數據類型,用來存放編譯后的正則表達式,它的成員re_nsub用來存儲正則表達式中的子正則表達式的個數,子正則表達式就是用圓括號包起來的部分表達式。
regex
描述正則表達式的字符串。
cflags
決定編譯的類型。
REG_EXTENDED
擴展的正則表達式,如果不設置則為基本的正則表達式。
REG_ICASE
不區分大小寫。
REG_NOSUB
不用存儲匹配后的結果。
如果設置該標志位,那么在regexec將忽略nmatch和pmatch兩個參數。
REG_NEWLINE
識別換行符。
‘^’匹配行的開頭, 不管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);
功能
根據編譯好的正則表達式,對字符串進行匹配。
參數含義
preg
經過regcomp編譯的正則表達式。
string
待匹配的字符串。
nmatch
匹配到的字符串的個數。
pmatch
匹配到的數組。
regmatch_t 的定義如下
typedef struct {
regoff_t rm_so;
regoff_t rm_eo;
} regmatch_t;
rm_so
如果-1==rm_so,表示沒有匹配到。
如果-1!=rm_so,表示string中下一個最大子字符串的偏移量(舉例string開頭的偏移量)。
rm_eo
子字符串的長度。
eflags
REG_NOTBOL
NOTBOL = not-begin-of-line
不匹配行的開頭,除非在regcomp編譯時cflag設置REG_NEWLINE。
該標志位可能在如下情況使用,傳遞string的不同部分給regexec,並且string的開頭不能被解釋成行的開頭。
REG_NOTEOL
NOTEOL = not-end-of-line
不匹配行的結束,除非在regcomp編譯時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
經過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
非法使用重復的操作符,例如在第一個字符使用 ’*’
REG_EBRACE
EBRACE=error brace
大括號不匹配
REG_EBRACK
EBRACK=error bracket
中括號不匹配
REG_ECOLLATE
ECOLLATE=error collate
非法參數
REG_ECTYPE
ECTYPE=error character type
錯誤的字符類型
REG_EEND
EEND=error end
無具體錯誤。該錯誤碼在POSIX.2中沒有定義
REG_EESCAPE
EESCAPE=error escape
多余的\
REG_EPAREN
EPAREN=error parenthesis
圓括號不匹配
REG_ERANGE
ERANGE=error range
非法使用范圍操作符,例如結束的標識出現在開始標識之前
REG_ESIZE
ESIZE=error size
編譯正則表達式需要的內存大於64kb,在POSIX.2中沒有定義
REG_ESPACE
ESPACE=error space
編譯正則表達式已經超出內存空間
REG_ESUBREG
ESUBREG=error reference to a subexpression
非法使用子表達式的引用
1.3.4 regefree
函數原型
void regfree(regex_t *preg);
功能
釋放由regcomp編譯preg的內存。
參數含義
preg
經過regcomp編譯的正則表達式。
返回值
void
1.4 POSIX正則表達式規范
參考:http://en.wikipedia.org/wiki/Regular_expression
POSIX正則表達式分為Basic Regular Expressions 和 Extended Regular Expressions。
ERE增加支持?,+和|,去除了通配符()和{}。而且POSIX正則表達式的標准語法經常堅持使用附加的語法來支持特殊應用。雖然POSIX.2沒有實現一些具體的細節,BRE和ERE提供被很多工具使用的標准。
BRE要求通配符()和{}寫成\(\)和\{\},ERE中無需這樣。
1.4.1 基本通配符
舉例
.at
匹配任何以”at”結尾長度為3的字符串,例如” at”,”aat”,”cat”等
[hc]at
匹配”hat”和”cat”
[^b]at
除了”bat”,匹配任何以”at”結尾長度為3的字符串。例如,”aat”,”cat”等
[^hc]at
除了”hat”和”cat”,匹配任何以”at”結尾長度為3的字符串。例如,”aat”,”tat”等
^[hc]at
匹配任何以”hat”或”cat”開頭的字符串或行
[hc]at$
匹配任何以”hat”或”cat”結尾的字符串或行
匹配任何三個字符,第一個和第三個字符必須分別為”[”,”]”,因為”\[”和”\[”是經過轉義,例如”[a]”,”[b]”
s.*
匹配任何以”s”開頭的字符串,例如”swa”,”seed”
1.4.2 Extended Regular Expressions
在ERE中,反斜杠’\’用來對通配符進行轉義,所以BRE中的’\(’和’\)’在ERE中改為’(’和’)’,’\{’和’\}’改為’{’和’}’。 ERE移除了’\n’通配符,並添加了如下通配符。
通配符 |
描述 |
? |
匹配前一個元素0次或1次。例如,ab?c匹配”ac”,”abc”。 |
+ |
匹配前一個元素1次或多次。例如,ab+c匹配”abc”,”abbc”等,但是不能匹配”ac”。 |
| |
匹配前一個表達式或后一個表達式。例如,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。
當使用范圍通配符時,例如[a-z]。計算機本地設置決定了字符編碼的順序。計算機可能按a-z的順序來存儲,或者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] |
小寫字母字符 |
[:print:] |
[\x20-\x7E] |
可視字符和空格 |
[:punct:] |
[][!"#$%&'()*+,./:;<=>?@\^_`{|}~-] |
標點符號 |
[:space:] |
[ \t\r\n\v\f] |
空白字符 |
[:upper:] |
[A-Z] |
大寫字母字符 |
[:xdigit:] |
[A-Fa-f0-9] |
十六進制字符 |
POSIX定義的character class只能在中括號內使用。例如,[[:upper:]ab]匹配大寫字母字符和”a”,”b”。
[:word:]是附加的非POSIX的character class,[:word:]表示[:alnum:]和下划線。這表明在很多編程語言中,這些通配符可能是標識符。
1.5 源碼示例
我的github:https://github.com/loverszhaokai/libutil/blob/master/src/regex_util.cc
libutil庫中包含有regex的示例。歡迎批評指正,一起修改。
/****************************************************************************** libutil Author: zhaokai Email: loverszhao@gmail.com Reference: POSIX Description: Version: 1.0 ******************************************************************************/ #include "util/regex_util.h" #include <algorithm> #include <climits> #include <iostream> #include <regex.h> namespace util { bool RegexMatch(const std::string &_pattern, const std::string &_input, std::vector<std::string> *_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 < nmatch; ++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; } bool FullMatch(const std::string &_pattern, const std::string &_input) { std::vector<std::string> output; std::string err_msg = ""; bool result = RegexMatch(_pattern, _input, &output, &err_msg); if (!result) { std::cout << err_msg << std::endl; return false; } else { // full-match in case of @_input == output[0] if (0 < output.size() && _input == output[0]) return true; } return false; } }; // namespace util
單元測試文件,包含了Mac,IP,Port,文件夾路徑的正則表達式測試
/****************************************************************************** libutil Author: zhaokai Email: loverszhao@gmail.com Reference: chromium Description: 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::vector<std::string> 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 < ARRAYSIZE_UNSAFE(cases); ++i) { std::string err_msg = ""; std::vector<std::string> output; EXPECT_EQ(cases[i].result, RegexMatch(cases[i].pattern, cases[i].input, &output, &err_msg)) << "cases[" << i << "]"; EXPECT_EQ(cases[i].expect_output, output) << "cases[" << i << "]"; } } 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 < ARRAYSIZE_UNSAFE(cases); ++i) { EXPECT_EQ(cases[i].result, FullMatch(cases[i].pattern, cases[i].input)) << "cases[" << i << "] pattern=" << cases[i].pattern << " input=" << cases[i].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 otherwise bool ValidateMac(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 otherwise bool ValidateIP(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 otherwise bool ValidatePort(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 otherwise bool ValidatePath(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 < ARRAYSIZE_UNSAFE(cases); ++i) { EXPECT_EQ(cases[i].result, ValidateMac(cases[i].input)) << "cases[" << i << "] input=" << cases[i].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 < ARRAYSIZE_UNSAFE(cases); ++i) { EXPECT_EQ(cases[i].result, ValidateIP(cases[i].input)) << "cases[" << i << "] input=" << cases[i].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 < ARRAYSIZE_UNSAFE(cases); ++i) { EXPECT_EQ(cases[i].result, ValidatePort(cases[i].input)) << "cases[" << i << "] input=" << cases[i].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 < ARRAYSIZE_UNSAFE(cases); ++i) { EXPECT_EQ(cases[i].result, ValidatePath(cases[i].input)) << "cases[" << i << "] input=" << cases[i].input << std::endl; } } } }; // namespace util