C 正则表达式

最近做项目,要检查一下 ip 值的格式,最好的方法当然是用正则表达式,但是我以前从来没用 C 写过正则表达式,刚好记录一下,方便以后查看。

1 概述

C 语言本身不支持正则表达式,但是可以使用函数库,我决定使用 Linux 发行版自带的这个。

2 使用

  1. 引入头文件

    1
    2
    #include <sys/types.h>
    #include <regex.h>
    
  2. 可用函数及解析

    可使用的函数主要有 4 个:

    • regecomp编译
    1
    int regcomp(regex_t *preg, const char *regex, int cflags);
    

    该函数用来将regex提供的字符串‘编译’成pregcflags决定了如何‘编译’该字符串。此处的‘编译’事实上是将字符串解析成一个re_pattern_buffer结构体,之后的正则表达式相关操作,都要使用该变量。

    cflags可‘按位或’0 或者一下选项:

    1
    2
    3
    4
    REG_EXTENDED	/* 使用 POSIX 扩展的正则表达式规则来解析 regex */
    REG_ICASE		/* 使用该 re_pattern_buffer 搜索时不区分大小写 */
    REG_NOSUB		/* 不反回匹配地址,即忽略 regexec 中的 nmatch 和 pmatch */
    REG_NEWLINE		/* 识别换行符 */
    

    成功返回 0,失败返回errcode

    • regexec匹配
    1
    2
    int regexec(const regex_t *preg, const char *string, size_t nmatch,
    			regmatch_t pmatch[], int eflags);
    

    该函数将string字符串用来与preg进行正则匹配,匹配结果存放在nmatchpmatch中,eflags指定匹配方式。

    eflags可‘按位或’0 或以下选项:

    1
    2
    3
    4
    REB_NOTBOL 		/* 从头匹配行操作符总是匹配失败, 当一个字符串分几个部分传给 regexec 时,
    				* 这个时候字符串的开头并不是这一行的开头 
    				*/
    REG_NOTEOL		/* 行结尾匹配操作符总是匹配失败 */
    

    regmatch_t结构如下:

    1
    2
    3
    4
    5
    typedef struct
    {
      regoff_t rm_so;  /* Byte offset from string's start to substring's start.  */
      regoff_t rm_eo;  /* Byte offset from string's start to substring's end.  */
    } regmatch_t;
    

    匹配成功返回 0,失败返回REG_NOMATCH

    • regerror错误
    1
    2
    size_t regerror(int errcode, const regex_t *preg, char *errbuf,
    				size_t errbuf_size);
    

    该函数用来将regcompregexec返回的错误码,转化为 string,放置到errbuf中。errcode为错误码,preg为对应的正则表达式结构体。

    • regfree释放
    1
    void regfree(regex_t * preg);
    

    regfree释放preg

    • 错误码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    typedef enum
    {
    #if defined _XOPEN_SOURCE || defined __USE_XOPEN2K
      REG_ENOSYS = -1,	/* This will never happen for this implementation.  */
    #endif
       
      REG_NOERROR = 0,	/* Success.  */
      REG_NOMATCH,		/* Didn't find a match (for regexec).  */
       
      /* POSIX regcomp return error codes.  (In the order listed in the
         standard.)  */
      REG_BADPAT,		/* Invalid pattern.  */
      REG_ECOLLATE,		/* Invalid collating element.  */
      REG_ECTYPE,		/* Invalid character class name.  */
      REG_EESCAPE,		/* Trailing backslash.  */
      REG_ESUBREG,		/* Invalid back reference.  */
      REG_EBRACK,		/* Unmatched left bracket.  */
      REG_EPAREN,		/* Parenthesis imbalance.  */
      REG_EBRACE,		/* Unmatched \{.  */
      REG_BADBR,		/* Invalid contents of \{\}.  */
      REG_ERANGE,		/* Invalid range end.  */
      REG_ESPACE,		/* Ran out of memory.  */
      REG_BADRPT,		/* No preceding re for repetition op.  */
       
      /* Error codes we've added.  */
      REG_EEND,		/* Premature end.  */
      REG_ESIZE,		/* Compiled pattern bigger than 2^16 bytes.  */
      REG_ERPAREN		/* Unmatched ) or \); not returned from regcomp.  */
    } reg_errcode_t;
    
  3. 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* ip 值检查器 */
static int
ip_checker(char* str)
{
    const char* ip_regex = "^((25[0-5])|(2[0-4][0-9]])|(1[0-9][0-9])|([1-9][0-9])|[0-9])(.((25[0-5])|(2[0-4][0-9])|(1[0-9][0-9])|([1-9][0-9])|[0-9])){3}$";
    regex_t* preg;
    int error_code, reti;
    char error_buf[256];

    preg = (regex_t*)malloc(sizeof(regex_t));

    error_code = regcomp(preg, ip_regex, REG_EXTENDED | REG_NOSUB);

    if (error_code)
    {
        regerror(error_code, preg, error_buf, 256);
        log_error("ip regular expression compile error.");
        log_error("error code: %d %s", error_code, error_buf);
        regfree(preg);
        return -1;
    }

    reti = regexec(preg, str, 0, NULL, 0);

    if (reti != REG_NOERROR)
    {
        log_debug("ip: %s does not match ip regex.", str);
        return -1;
    }

    return 0;
}