POSIX正则表达式

目录

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思是pointerreg意思是regex_t类型。

  regex_t是一个结构体数据类型,用来存放编译后的正则表达式,它的成员re_nsub用来存储正则表达式中的子正则表达式的个数,子正则表达式就是用圆括号包起来的部分表达式。

regex

  描述正则表达式的字符串。

cflags

  决定编译的类型。

    REG_EXTENDED

  扩展的正则表达式,如果不设置则为基本的正则表达式。

    REG_ICASE

  不区分大小写。

   REG_NOSUB

  不用存储匹配后的结果。

  如果设置该标志位,那么在regexec将忽略nmatchpmatch两个参数。

   REG_NEWLINE

  识别换行符。

      ‘^’匹配行的开头, 不管regexec中是否设置eflagsREG_NOTBOL

   ‘$’匹配行的末尾, 不管regexec中是否设置eflagsREG_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);

功能

  将regcompregexec返回的errorcode转换成错误信息。

参数含义

errorcode

  错误码,由regcompregexec获得。

preg

  经过regcomp编译的正则表达式。

errbuf

  存储错误信息的buffer

errbuf_size

  errbuf的大小。

返回值

  返回errbuf存储错误信息所需要的大小。

  如果errbuferrbuf_size都是非0,那么errbuf中有errbuf_size-1大小错误信息字符串,最后是字符串结束标记(‘\0’)

错误码

regcomp方法返回的错误码
   REG_BADBR

    BR=back reference

    非法使用返回引用操作符

   REG_BADPAT

    PAT=pattern

    非法使用正则表达式操作符,例如grouplist

   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没有实现一些具体的细节,BREERE提供被很多工具使用的标准。

  BRE要求通配符(){}写成\(\)\{\}ERE中无需这样。

1.4.1     基本通配符

通配符

描述

.

匹配任何一个单字符(许多应用不包括换行符,但是假设包括换行符也是安全的)。在大中小括号中,该通配符只匹配字符’.’,例如,a.c匹配”abc”,但是[a.c]只匹配”a””.””c”。要匹配’.’不能使用’\.’,因为’\.’的作用与’.’一样,应该使用[.]来匹配”a.c”中的点。

[]

只匹配中括号内的一个字符。例如[abc]匹配”a””b””c”[a-z]匹配小写的”a””z”。这些格式可以混合使用:[abcx-z]匹配”a”,”b”,”c”,”x”,”y”,”z”[a-cx-z]的效果相同。

‘-’如果出现在[]的开头或结尾,表示匹配字符’-’,例如[^-abc][-abc] [abc-]。注意不能使用’\’

‘]’可以出现在中括号中的第一个位置,例如[]abc][^]abc]

[^ ]

匹配单个字符,该字符不能包含在中括号中。例如,[^abc]匹配任何除’a’,’b’,’c’外的字符。[^a-z]匹配任何除’a’-‘z’的字符。同理,字符与范围标识可以混合使用,例如,[^a-cx-z]

^

匹配字符串的开头。在基于行的工具中,匹配每一行的开头。

$

匹配字符串的结尾或者换行符的前一个位置。在基于行的工具中,匹配每一行的结尾。

()

定义一个子表达式。圆括号在整体匹配完后进行匹配。子表达式也叫做块或组。

BRE模式需要写成\(\)

\n

匹配第n(1<=n<=9)个子表达式。POSIX.2对该通配符的定义很模糊,有的工具允许引用大于9的子表达式。

*

匹配前一个元素0次或多次。例如,ab*c匹配”ac”,”abc”,”abbbbbc”等。[xyz]*匹配””,”x”,”y”,”z”,”zx”,”zyx”,”xyzzy”等。(ab)*匹配””,”ab”,”abab”等。

{m,n}

匹配前一个元素至少m次,至多n次。例如,a{3,5}匹配”aaa”,”aaaa”,”aaaaa”

BRE模式需要写成\{m,n\}

 

举例

.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应用于BREERE

当使用范围通配符时,例如[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:]是附加的非POSIXcharacter class[:word:]表示[:alnum:]和下划线。这表明在很多编程语言中,这些通配符可能是标识符。

1.5     源码示例

我的githubhttps://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(&reg, _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(&reg, _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, &reg, errbuf, errbuf_size);
    *_err_msg = errbuf;
    
    regfree(&reg);
    return false;
  }

  regfree(&reg);
  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

 

posted @ 2014-05-01 21:10  loverszhaokai  阅读(7570)  评论(1编辑  收藏  举报