使用POSIX正则库匹配一行中多个结果

时间:2022-02-22 13:37:46

正则匹配与正则表达式是什么东西我就不说了,在这里说下POSIX这个c语言正则库在对字符串进行正则匹配时取出多个结果的问题。

首先简单说明下POSIX正则库的几个函数和使用方法

  第一个函数:int regcomp(regex_t *preg, const char *regex, int cflags); POSIX C正则库为了提高效率,在将一个字符串与正则表达式进行比较之前,首先要用regcomp()函数对它进行编译,将其转化为regex_t类型。

  preg 编译后的regex_t数据   

  regex 正则表达式

  cflags 设置相关标志,包括 REG_EXTENDED、REG_ICASE、REG_NOSUB、REG_NEWLINE

  正确编译正则表达式则返回0否则返回一个错误码

第二个函数:int regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags);

  该函数的功能就是将编译过后的正则表达式与在进行匹配的字符串进行正则匹配

  preg 经regcomp编译后的参数

   string 要进行正则匹配的字符串

   nmatch pmatch数组的个数

   pmatch 该数组中的两个参数rm_so与rm_eo表示匹配之后匹配的字符串在string中的偏移地址的首地址与尾地址

  eflags 设置相关标志,包括REG_NOTBOL和REG_NOTEOL

  函数返回的结果与regcomp相同

第三个函数:size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size);

  该函数看名字就能猜到,它是通过错误代码返回错误信息的

  errcode 是regcomp或者regexec返回的错误码

  preg 是经regcomp编译后的数据

  errbuf 返回错误信息的缓冲区

  errbuf_size 错误信息缓冲区的大小

  该函数返回的结果是错误信息的长度

第四个函数:void regfree(regex_t *preg); 很简单,释放内存的

使用这四个函数就可以进行正则匹配了,使用的方法是先使用regcomp对正则表达式进行编译,然后通过regexec进行正则匹配,匹配成功后会在结构体regmatch_t中的两个参数中设置匹配字符串在字符串的起始位置与结束位的偏移量,而传递给regexec中的regmatch_t结构体是一个数组,所以你一定认为标题中获取多个结果的方法就是设置这个数组的大小。起初我也是这么想的,但当我这么做的时候才发现,结果并非这样,一次调用 regexec返回的结果只有一个,没有多个,那么这个结构体数组是怎么回事呢?

man手册中说的是返回的子表达式,这个子表达式是什么?我来举一个例子 假如有这么一个字符串 <title>第一个</title>第二个<title>第三个</title><title>第四个</title> 如何我们想找出这个字符串中界于<title>与</title>之间中的字符串也就是字符串”第一个、第三个、第四个”,那么我们可能会通过这个正则表达式来获取想要的字符串 <title>.[^>]*   ,但这样获取的字符串中保有所以为了只取<title>与</title>中间的字符串我们可以使用子表达式来完成,那么这样这个正则表达式就是<title>\(.[^>]*\)

这样我们就可以通过pmatch[1]来定位取得的子表达式中相对于字符串的偏移,所以显然,pmatch[2]表示的是第二个子表达式,pmatch[3]表示的是第三个子表达式,而pmatch[0]则是整个正则表达式匹配的结果,于是想要在字符串中匹配出所有的<title>与</title>中的结果时就需要多次调用regexec函数。

既然已经知道了regmatch_t这个结构体数组并不像我们想象中的那样可以返回匹配的多个结果,那么如何在一个字符串中进行多次的 regexec调用进行匹配呢?这其实很简单,如何我们需要匹配的字符串是多行的,那么可以按着每一行调用一次regexec进行匹配,但你也看到了,就像上面的例子,你会很有可能在一行中匹配出多个结果,所以我们需要另一种方法,其实也很简单,我们对字符串的指针进行位移形成一个”新的字符串”就可以了。

因为regexec匹配后在pmatch[0]中很返回此次匹配字符串的未位偏移量,所以我们只需要将需要进行匹配的字符串的首地址移动到该处并再次进行下一次regexec的调用匹配就可以了,并如此循环,只到全部匹配完成。以上面的例子来说

<title>第一个</title>第二个</title><title>第三个</title><title>第四个</title>

第一次匹配时匹配到的结果是<title>第一个,此时pmatch[0].rm_eo中的数字为 strlen(“<title>第一个”),所以我们将这个字符串的首地址进行移动,将首地址移动到<title>第一个后面,这时”新的字符串”就是

</title>第二个</title><title>第三个</title><title>第四个</title>

如此类推就可以匹配到所有的结果。

最后以一个匹配百度贴吧的发帖时间与帖子标题为例给出一个c语言源代码

     #include <stdio.h>
  1. #include <curl/curl.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <string.h>
  5. #include <regex.h>
  6. #include <iconv.h>
  7. #include <errno.h>
  8. typedef struct
  9. {
  10.  int len; //当前字符串data的长度
  11.  char *data; //返回的字符串
  12. }DATA; //保存libcurl回调函数中返回的数据
  13. typedef struct node
  14. {
  15.  char *url;
  16.  char *topic;
  17.  struct node *next;
  18. }LIST; //一个链表结构,用来缓存第一次扫描贴吧时帖子的地址与标题
  19. size_t get_data(char *ptr,size_t size,size_t nmemb,DATA *data) //libcurl返回数据的回调函数,使用动态字符串的方式保存返回的数据
  20. {
  21.  if(data->len)
  22.  {
  23.   char *temp;
  24.   temp=malloc(data->len+1);
  25.   snprintf(temp,data->;len+1,"%s",data->data);
  26.   free(data->data);
  27.   data->data=malloc(data->len+nmemb+1);
  28.   snprintf(data-&gt;data,data->len+nmemb+1,"%s%s",temp,ptr);
  29.   free(temp);
  30.  }
  31.  else
  32.  {
  33.   data->data=malloc(nmemb+1);
  34.   snprintf(data->data,nmemb+1,"%s",ptr);
  35.  }
  36.  data->len+=nmemb;
  37.  return nmemb;
  38. }
  39. LIST *list_init(void) //链表的初始化
  40. {
  41.  LIST *list;
  42.  list=malloc(sizeof(LIST));
  43.  list->url=NULL;
  44.  list->topic=NULL;
  45.  list->next=NULL;
  46.  return list;
  47. }
  48. void list_add(LIST *list,char *url,char *topic) //将链表中添加数据
  49. {
  50.  LIST *temp;
  51.  while(list->next)
  52.   list=list->next;
  53.  temp=malloc(sizeof(LIST));
  54.  temp->url=malloc(strlen(url)+1);
  55.  snprintf(temp->url,strlen(url)+1,"%s",url);
  56.  temp->topic=malloc(strlen(topic)+1);
  57.  snprintf(temp->topic,strlen(topic)+1,"%s",topic);
  58.  temp->next=NULL;
  59.  list->next=temp;
  60. }
  61. void list_destroy(LIST *list) //销毁链表,释放内存
  62. {
  63.  LIST *temp;
  64.  while(list->next)
  65.  {
  66.   list=list->next;
  67.   temp=list;
  68.   free(list->url);
  69.   free(list->topic);
  70.   free(temp);
  71.  }
  72. }
  73. int gbk_to_utf8(char *in,char *out,size_t out_bytes) //由于百度贴吧使用的是GBK编码,而我本机是使用UTF-8编码的,所以需要进行编码转换
  74. {
  75.  iconv_t cd;
  76.  size_t in_bytes=strlen(in);
  77.  if((cd=iconv_open("UTF-8//","GBK//IGNORE")) == (iconv_t)-1)
  78.   return -1;
  79.  if(iconv(cd,&in,&in_bytes,&out,&out_bytes) == -1)
  80.   return -1;
  81.  iconv_close(cd);
  82.  return 0;
  83. }
  84. LIST *topic_list(char *str) //第一次扫描百度贴吧,获取url与标题并缓存在链表中
  85. {
  86.  LIST *list=NULL;
  87.  regex_t reg;
  88.  regmatch_t pmatch[3]; //两个子表达式
  89.  char topic[1024]={0};
  90.  char url[128]={0};
  91.  char temp[1024]={0};
  92.  list=list_init();
  93.  if(regcomp(&reg,"href=\"\\(/p/[0-9]\\{6,10\\}\\)\" title=\"\\(.[^\"]*\\)",0) != 0) //对贴吧中含有帖子与帖子链接的字符进行匹配的正则表达式,并使用子表达式匹配出链接与标题
  94.  {
  95.   perror("regcomp"); //这里只是简单地debug,正确的提取错误的方法是使用regerror
  96.   regfree(&reg);
  97.   return NULL;
  98.  }
  99.  while(regexec(&reg,str,3,pmatch,0) == 0) //循环匹配
  100.  {
  101.   bzero(url,sizeof(url));
  102.   snprintf(url,pmatch[1].rm_eo-pmatch[1].rm_so+23,
  103.     "http://tieba.baidu.com%s",str+pmatch[1].rm_so); //取出第一个子表达式即帖子链接地址
  104.   bzero(temp,sizeof(temp));
  105.   snprintf(temp,pmatch[2].rm_eo-pmatch[2].rm_so+1,
  106.     "%s",str+pmatch[2].rm_so); //取出第二个子表达式结果即帖子的标题
  107.   bzero(topic,sizeof(topic));
  108.   gbk_to_utf8(temp,topic,sizeof(topic)-1); //进行编码转换
  109.   list_add(list,url,topic); //将结果缓存到链表中
  110.   str+=pmatch[0].rm_eo; //移动字符串首地址到前面匹配的结果的未尾
  111.  }
  112.  regfree(&reg);
  113.  return list;
  114. }
  115. void list_print(LIST *list) //打印链表中的内容
  116. {
  117.  while(list->next)
  118.  {
  119.   list=list->next;
  120.   printf("%s %s\n",list->url,list->topic);
  121.  }
  122. }
  123. void print_topic_and_post_time(CURL *curl,LIST *list,char *type) //打印标题与发帖时间的函数
  124. {
  125.  regex_t reg;
  126.  regmatch_t pmatch[1];
  127.  char temp[20];
  128.  DATA data;
  129.  LIST *head;
  130.  if(strcmp(type,"1") == 0)
  131.   head=list_init();
  132.  curl_easy_setopt(curl,CURLOPT_WRITEDATA,&data);
  133.  if(regcomp(&reg,"[0-9]\\{4\\}-[0-9]\\{2\\}-[0-9]\\{2\\} [0-9]\\{2\\}:[0-9]\\{2\\}",0) != 0) //匹配出发帖时间的正则表达式
  134.   return;
  135.  while(list->next) //从缓存链表中取出链接进行访问并匹配出发帖时间
  136.  {
  137.   list=list->next;
  138.   curl_easy_setopt(curl,CURLOPT_URL,list->url);
  139.   data.data=NULL;
  140.   data.len=0;
  141.   curl_easy_perform(curl);
  142.   if(data.len)
  143.   {
  144.    bzero(temp,sizeof(temp));
  145.    if(regexec(&reg,data.data,1,pmatch,0) != 0)
  146.     snprintf(temp,sizeof(temp),"未知发表时间");
  147.    else
  148.     snprintf(temp,pmatch[0].rm_eo-pmatch[0].rm_so+1,"%s",data.data+pmatch[0].rm_so); //取出发匹配结果
  149.    if(strcmp(type,"1") == 0) // 由于获取帖子标题和发帖时间时链接不同,所以要分两步进行访问,第一步访问缓存地址与标题,第二步访问缓存中缓存的链接,所以在时间上可能会很长,百度贴吧一页的帖子数量在50帖,这里采用两种方法进行输出,一、访问所有帖子链接并缓存到链表中然后一次性打印,二、每访问一个帖子链接便打印一次,显然由于数量过多,前种方法在等待屏幕的输出方面需要一定的时间
  150.     list_add(head,temp,list->topic);
  151.    else
  152.     printf("%s %s\n",temp,list->topic);
  153.    free(data.data);
  154.   }
  155.  }
  156.  regfree(&reg);
  157.  if(strcmp(type,"1") == 0)
  158.  {
  159.   list_print(head);
  160.   list_destroy(head);
  161.  }
  162. }
  163. int main(int argc,char **argv)
  164. {
  165.  CURL *curl;
  166.  CURLcode code;
  167.  char tieba[128]={0};
  168.  DATA data;
  169.  LIST *list;
  170.  data.len=0;
  171.  data.data=NULL;
  172.  if(argc < 3)
  173.  {
  174.   printf("tieba  [1|an other]\n");
  175.   return -1;
  176.  }
  177.  snprintf(tieba,sizeof(tieba),"http://tieba.baidu.com/f?kw=%s",argv[1]); //贴吧链接
  178.  curl=curl_easy_init();
  179.  curl_easy_setopt(curl,CURLOPT_URL,tieba);
  180.  curl_easy_setopt(curl,CURLOPT_WRITEFUNCTION,get_data); //设置回调函数
  181.  curl_easy_setopt(curl,CURLOPT_WRITEDATA,&data); //设置回调函数的参数
  182.  code=curl_easy_perform(curl);
  183.  if(code != 0)
  184.   return -1;
  185.  if(data.len)
  186.  {
  187.   list=topic_list(data.data); //缓存帖子标题与地址
  188.   free(data.data);
  189.   if(list)
  190.   {
  191.    print_topic_and_post_time(curl,list,argv[2]); //打印帖子标题与发帖时间
  192.    list_destroy(list);
  193.   }
  194.  }
  195.  curl_easy_cleanup(curl);
  196.  return 0;
  197. }

http://blog.163.com/lixiangqiu_9202/blog/static/53575037201412311211291/