zoukankan      html  css  js  c++  java
  • linux C语言处理正则表达式

    Linux下C语言处理正则表达式——regex.h

    具体函数介绍

    编译正则表达式函数

    int regcomp(regex_t *preg, const char *regex, int cflags);

    其中preg用于保存编译后的正则表达式,regex是我们写的正则表达式。cflags标志位后面再说。

    先说说regex_t结构体:

    对于这个结构体而言,我们只要记住,它是编译后的正则表达式,后面的匹配是用编译后的正则表达式,这样效率更高,而不是使用我们自己写的原始的正则表达式。此外,还要知道regex_t有一个成员re_nsub,它表示“子正则表达式的个数”。所谓“子正则表达式”就是圆括号里面的正则表达式。可能还是有点懵。没关系,慢慢来!我们使用正则表达式的一个主要目的是提取满足条件的部分。比如有个字符串username=阿星&sex=女,现在我们想提取用户名,也是就“阿星”,那么我们的正则表达式应该写成: username=([^&]*)&,也就是将匹配“阿星”的正则表达式放到圆括号中,作为整个表达式的一个子表达式。后面我们执行regexec函数后,就可以得到“阿星”(后面再讲)。

    再来说说cflags:

    cflags 的取值有:REG_EXTENDED、REG_ICASE、REG_NOSUB和REG_NEWLINE。这四个值可以单独使用,也可以用按位与联合使用。

    其中:

    REG_EXTENDED:

    意思是,解释正则表达式时使用扩展的正则表达式语法。POSIX规范将正则表达式的实现方法分为了两种:基本正则表达式(BRE)和扩展正则表 达式(ERE)

    BRE和ERE到底有什么区别?其实仅仅是元字符的不同!在BRE方式中,只承认^ 、$、 . 、[ 、] 、*这些是元字符,所有其他的字符都被识别为文字字符。而ERE中,则添加了(、 ) 、{ 、} 、?、 + |、等元字符(及其相关功能)。grep命令默认支持BRE,要想支持ERE需要使用-E选项。

    REG_ICASE:

    如果编译正则表达式时使用了这个标志,那么在用regexec()函数进行匹配时,就忽略大小写。

    REG_NOSUB:

    如果使用了这个选项得到的编译后的正则表达式,在被后面的regexec()函数使用时,regexec()的nmatch参数和pmatch参数将会被忽略(后面再讲)

    REG_NEWLINE:

    一开始我对这个标志位的理解非常模糊,网上很多人解释的也不清楚。经过我的反复试验,终于明白了。

    其实REG_NEWLINE的作用就两个:

    1、         使^和$有效。

    2、         绝对不匹配换行符。

    相信大家也都看过Linux中的man page。对于REG_NEWLINE这个标志位的解释,在man page中用了四句话。

    我们先来看后两句:

    Match-beginning-of-line operator (^) matches  the  empty  string immediately  after  a newline, regardless of whether eflags, the execution flags of regexec(), contains REG_NOTBOL.

    Match-end-of-line operator ($) matches the empty string  immediately  before  a  newline, regardless of whether eflags contains REG_NOTEOL.

    这两句的意思其实就是,是^匹配一行的开始位置,$匹配一行的结束位置(如果没有使用REG_NEWLINE,这两个字符将被当做普通字符)。并且使REG_NOTBOL和REG_NOTEOL无效。

    举两个例子:

    有字符串:

    username=xinger&sex=girl&age=22 school=BIT&husband=qinger &like=study&look=pretty

    如果我们没有使用REG_NEWLINE标志,那么正则表达式^school=([^&]*)将不能匹配,因为这里^被解释成了普通字符,而不是一行的开始。

    如果我们加上REG_NEWLINE标志,那么将匹配成school=BIT,此时^不再是普通字符,而是匹配一行的开始。

    再比如正则表达式age=([^$]*),如果没有使用REG_NEWLINE,将匹配成:

    age=22 school=BIT&husband=qinger &like=study&look=pretty

    还是因为$被解释成了普通字符,

    比如我们在原字符串中添加一个$,变成

    username=xinger&sex=girl&age=22 school=$BIT&husband=$qinger &like=study&look=pretty

    那么匹配结果变成了age=22 school=,原因依然是:把$当成了普通字符。

    在来看前两句:

    Match-any-character operators don't match a newline.

    A  nonmatching  list ([^...])  not containing a newline does not match a newline.

    这两句的意思说白了,就是保证不匹配换行符。比如第一句,意思是匹配任意字符的元字符也不匹配新的一行(好乱呀)。什么意思呢?就是比如说点(.)本来匹配所有的字符(注意,在POSIX中点匹配所有字符!!!和我们平时学的不一样。)但是如果使用了REG_NEWLINE标志,则不匹配换行符 。

    还是举个例子吧:

    有字符串:

    username=xinger&sex=girl&age=22 school=BIT&husband=$qinger &like=study&look=pretty

    有正则表达式:sex=([^@]*),如果没有REG_NEWLINE标志,匹配结果是:

    sex=girl&age=22 school=BIT&husband=$qingerr &like=study&look=pretty

    因为[^@]匹配所有不是@的字符。但是如果我们加上了REG_NEWLINE,那么匹配结果为:sex=girl&age=22,原因是REG_NEWLINE保证了绝不匹配换行符!!!其实就相当于[^@ ]不加REG_NEWLINE。

    在比如,有正则表达式sex=(.*),我们前面提到过:点在POSIX中匹配任意字符(’’除外),所以点也匹配换行符,所以匹配结果为:

    sex=girl&age=22 school=BIT&husband=$qinger &like=study&look=pretty

    但是,如果我们使用了REG_NEWLINE,则保证不会匹配换行符,匹配结果就变成了:sex=girl&age=22。

    最后说说返回值:

    成功返回0,失败可以用regerror获取失败码。

    用编译后的正则表达式进行匹配

    int regexec(const regex_t *preg, const char *string, size_t nmatch,
    
                       regmatch_t pmatch[], int eflags);

    regexec函数用上一步中编译好的正则表达式preg对string内容进行匹配,并将匹配结果以记录字节偏移量的形式保存在pmatch数组中。

    首先看看regmatch_t结构体:

    regmatch_t 是一个结构体数据类型,在regex.h中定义:            

    typedef struct
    
    {
    
       regoff_t rm_so;
    
       regoff_t rm_eo;
    
    } regmatch_t;

    regexec函数将用匹配的子字符串的起止地址填充pmatch结构体,pmatch[0]对应的是整个正则表达式的匹配结果的起止地址;pmatch[i]则对应存储了第i个子匹配字符串的起止地址。(rm_so表示起始位置距离首地址的偏移量,rm_eo表示结束位置距离首地址的偏移量+1,如果rm_so为-1则表示该子表达式没有匹配)。nmatch表示pmatch结构体数组的元素的个数,它至少应该是子表达式的个数加1(因为0下标存储的是整个表达式的匹配结果)。

    再来说说eflags:

    首先一点,如果regcomp中使用了REG_NEWLINE变量,这个标志位是无效的!

    这个标志位有两个取值:REG_NOTBOL和REG_NOTEOL,作用就是:如果设置了相应的标志位,那么含有^或$,而且含义是一行开始,或结束(比如^也可以解释成非),那么该正则表达式将永远不会匹配!!!。

    最后说说返回值:

    成功返回0,REG_NOMATCH表示失败。

    将错误码转换成错误信息

    size_t regerror(int errcode, const regex_t *preg, char *errbuf,
    
                           size_t errbuf_size);

    该函数用于将regcomp或regexec返回的错误码转换成错误字符串信息。

    参数errcode表示那两个函数返回的错误码,preg是regcomp编译后的正则表达式,errbuf用于存储错误信息字符串,errbuf_size是errbuf的大小。

    释放regex_t结构体

    void regfree(regex_t *preg);

    regcomp函数会填写regex_t结构体的元素,这之中需要为某些元素开辟存储空间,而regfree函数就是释放这些空间的。

    千万记得最后要调用regfree释放空间,否则会造成内存泄漏。

    最后附上一个小例子:

    #include <sys/types.h>
    #include <regex.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    char *pick_regex(const char *string,const char *pattern)
    {
        int err;
        char errbuf[1024];
        regex_t compiled;
        if((err = regcomp(&compiled,pattern,REG_EXTENDED|REG_ICASE|REG_NEWLINE)) != 0)
        {
             regerror(err,&compiled,errbuf,sizeof(errbuf));
             printf("err:%s
    ",errbuf);
            return NULL;
        }
        
        regmatch_t pmatch[2];
        err = regexec(&compiled,string,2,pmatch,REG_NOTBOL);
        if(err != 0)
        {
            printf("未匹配成功!
    ");
            return NULL;
        }
        if(compiled.re_nsub != 1)
            return NULL;
        if(pmatch[1].rm_so == -1)
            return NULL;
    
        int len = pmatch[1].rm_eo - pmatch[1].rm_so;
        char *value = (char *)malloc(len + 1);
        if(value == NULL)
            return NULL;
        memset(value,0,len + 1);
        memcpy(value,string + pmatch[1].rm_so,len);
        //free(value);
        regfree(&compiled);//切记最后要释放掉,否则会造成内存泄露
    
        return value;
    }
    
    int main()
    {
        const char *string = "username=xinger&sex=girl&age=22
    school=BIT&husband=qinger
    &like=study&look=pretty
    ";
        const char *pattern = "school=([^&]*)";
        char *value = pick_regex(string,pattern);
        printf("提取的值为:%s
    ",value);
    
        return 0;
    }

     如果你觉得对你有用,就点个赞吧~~~

  • 相关阅读:
    第5章 JDBC/ODBC服务器
    第4章 SparkSQL数据源
    第3章 SparkSQL解析
    第2章 执行SparkSQL查询
    第1章 Spark SQL概述
    Ubutun重启网卡
    Java面试通关要点汇总整理
    40道Java基础常见面试题及详细答案
    ListView
    数据库表及字段命名规范
  • 原文地址:https://www.cnblogs.com/qingergege/p/7359935.html
Copyright © 2011-2022 走看看