C正则表达式:提取实际匹配项

11
我正在使用C语言中的正则表达式(使用“regex.h”库)。在设置regcomp(...)和regexec(...)的标准调用(和检查)之后,我只能够打印与编译的正则表达式匹配的实际子字符串。根据手册页,使用regexec意味着您将子字符串匹配存储在称为“regmatch_t”的结构中。该结构仅包含rm_so和rm_eo以引用我理解为匹配子字符串在内存中的字符地址,但我的问题是如何仅使用这些偏移量和两个指针提取实际的子字符串并将其存储到数组中(理想情况下是一个二维字符串数组)?
当您只打印到标准输出时,它可以工作,但每当您尝试使用相同的设置但将其存储在字符串/字符数组中时,它会存储最初用于匹配表达式的整个字符串。此外,print语句中的“%.*s”是什么?我想象它本身就是一个正则表达式,可以正确读取字符数组的指针。我只想将匹配的子字符串存储在一个集合中,以便在软件中的其他地方使用它们。
背景:在进入下面代码中的while循环之前,p和p2都是指向要匹配的字符串开头的指针。[编辑:“matches”是一个二维数组,最终用于存储子字符串匹配项,并在您看到的主循环之前进行了预分配/初始化]
int ind = 0;
while(1){
    regExErr1 = regexec(&r, p, 10, m, 0);
    //printf("Did match regular expr, value %i\n", regExErr1);
    if( regExErr1 != 0 ){ 
        fprintf(stderr, "No more matches with the inherent regular expression!\n"); 
        break; 
    }   
    printf("What was found was: ");
    int i = 0;
    while(1){
        if(m[i].rm_so == -1){
            break;
        }
        int start = m[i].rm_so + (p - p2);
        int finish = m[i].rm_eo + (p - p2);
        strcpy(matches[ind], ("%.*s\n", (finish - start), p2 + start));
        printf("Storing:  %.*s", matches[ind]);
        ind++;
        printf("%.*s\n", (finish - start), p2 + start);
        i++;
    }
    p += m[0].rm_eo; // this will move the pointer p to the end of last matched pattern and on to the start of a new one
}
printf("We have in [0]:  %s\n", temp);
2个回答

12

有很多正则表达式包,但是你的似乎与POSIX中的一个匹配:regcomp()等。

它在<regex.h>中定义了两个结构:

  • regex_t至少包含size_t re_nsub,即括号子表达式的数量。

  • regmatch_t至少包含regoff_t rm_so,即从字符串开始到子字符串开始的字节偏移量,以及regoff_t rm_eo,即子字符串结束后第一个字符的字节偏移量。

请注意,'偏移量'不是指针,而是字符数组中的索引。

执行函数为:

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

您的打印代码应该是:

for (int i = 0; i <= r.re_nsub; i++)
{
    int start = m[i].rm_so;
    int finish = m[i].rm_eo;
//  strcpy(matches[ind], ("%.*s\n", (finish - start), p + start));  // Based on question
    sprintf(matches[ind], "%.*s\n", (finish - start), p + start);   // More plausible code
    printf("Storing:  %.*s\n", (finish - start), matches[ind]);     // Print once
    ind++;
    printf("%.*s\n", (finish - start), p + start);                  // Why print twice?
}

注意,应该升级代码以确保字符串复制(通过 sprintf())不会溢出目标字符串——可以使用 snprintf() 而不是 sprintf()。在打印时标记字符串的开始和结束也是一个好主意。例如:
    printf("<<%.*s>>\n", (finish - start), p + start);

这使得查看空格等变得更加容易。

[未来请尝试提供一个MCVE(Minimal, Complete, Verifiable Example)或SSCCE(Short,Self-Contained,Correct Example),以便人们更轻松地提供帮助。]

这是我创建的一个SSCCE,可能是作为对2010年另一个SO问题的回应之一。它是我保留的许多程序之一,我称之为“小品”; 这些小程序展示了某些功能的本质(例如在此情况下的POSIX正则表达式)。 我发现它们很有用作为记忆提示器。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <regex.h>

#define tofind    "^DAEMONS=\\(([^)]*)\\)[ \t]*$"

int main(int argc, char **argv)
{
    FILE *fp;
    char line[1024];
    int retval = 0;
    regex_t re;
    regmatch_t rm[2];
    //this file has this line "DAEMONS=(sysklogd network sshd !netfs !crond)"
    const char *filename = "/etc/rc.conf";

    if (argc > 1)
        filename = argv[1];

    if (regcomp(&re, tofind, REG_EXTENDED) != 0)
    {
        fprintf(stderr, "Failed to compile regex '%s'\n", tofind);
        return EXIT_FAILURE;
    }
    printf("Regex: %s\n", tofind);
    printf("Number of captured expressions: %zu\n", re.re_nsub);

    fp = fopen(filename, "r");
    if (fp == 0)
    {
        fprintf(stderr, "Failed to open file %s (%d: %s)\n", filename, errno, strerror(errno));
        return EXIT_FAILURE;
    }

    while ((fgets(line, 1024, fp)) != NULL)
    {
        line[strcspn(line, "\n")] = '\0';
        if ((retval = regexec(&re, line, 2, rm, 0)) == 0)
        {
            printf("<<%s>>\n", line);
            // Complete match
            printf("Line: <<%.*s>>\n", (int)(rm[0].rm_eo - rm[0].rm_so), line + rm[0].rm_so);
            // Match captured in (...) - the \( and \) match literal parenthesis
            printf("Text: <<%.*s>>\n", (int)(rm[1].rm_eo - rm[1].rm_so), line + rm[1].rm_so);
            char *src = line + rm[1].rm_so;
            char *end = line + rm[1].rm_eo;
            while (src < end)
            {
                size_t len = strcspn(src, " ");
                if (src + len > end)
                    len = end - src;
                printf("Name: <<%.*s>>\n", (int)len, src);
                src += len;
                src += strspn(src, " ");
            }
        }
    } 
    return EXIT_SUCCESS;
}

这个程序旨在查找文件/etc/rc.conf中以DAEMONS=开头的特定行(但您可以在命令行上指定替代文件名)。 您可以轻松地根据自己的需要进行调整。

是的,乔纳森,你说得对,我忘了提到我在使用POSIX进行正则表达式。你的代码建议解决了问题(打印),但是由于编译器的投诉,我不得不将其转换为while循环,因为显然我没有使用C99,所以不能使用for循环。 我还有一个问题:"<<%.*s>>"表达式是什么意思?(在<<>>内)为什么它需要两个参数(偏移减法计算和实际字符串)? - 9codeMan9
“<<”和“>>”只是一对“括号”字符,在我的工作中很少出现(但我不是整天玩XML或HTML)。您可以使用“[[%.*s]]”或“XX%.*sXX”或任何其他字符或字符,它们只是用于标记字符串的开始和结束。 - Jonathan Leffler
抱歉,我的表述不够清晰。我是指 %.*s 是什么意思?为什么要使用你提供的这两个参数? - 9codeMan9
1
啊,您需要阅读手册(printf()),但要注意 POSIX 扩展;它们有明显的标记。然而, * 表示有一个 int 参数(我应该添加强制转换以确保传递的是 int ),指定字符串的最大长度。如果在此之前出现 NUL '\0',则会停止,但当字符串较长时,由 * 参数指定的长度是最大打印长度。 - Jonathan Leffler
顺便提一下,如果您没有使用C99,可以在循环外部声明循环索引,然后只需编写 for (i = 0; i < r.re_nsub; i++) { ... } - Jonathan Leffler
显示剩余2条评论

0

由于g++的正则表达式存在漏洞,不知道什么时候才能修复,您可以使用我的代码代替(许可证:AGPL,无保修,风险自负,...)

/**
 * regexp (License: AGPL3 or higher)
 * @param re extended POSIX regular expression
 * @param nmatch maximum number of matches
 * @param str string to match
 * @return An array of char pointers. You have to free() the first element (string storage). the second element is the string matching the full regex, then come the submatches.
*/
char **regexp(char *re, int nmatch, char *str) {
  char **result;
  char *string;
  regex_t regex;
  regmatch_t *match;
  int i;

  match=malloc(nmatch*sizeof(*match));
  if (!result) {
    fprintf(stderr, "Out of memory !");
    return NULL;
  }

  if (regcomp(&regex, re, REG_EXTENDED)!=0) {
    fprintf(stderr, "Failed to compile regex '%s'\n", re);
    return NULL;
  }

  string=strdup(str);
  if (regexec(&regex,string,nmatch,match,0)) {
#ifdef DEBUG
    fprintf(stderr, "String '%s' does not match regex '%s'\n",str,re);
#endif
    free(string);
    return NULL;
  }

  result=malloc(sizeof(*result));
  if (!result) {
    fprintf(stderr, "Out of memory !");
    free(string);
    return NULL;
  }

  for (i=0; i<nmatch; ++i) {
    if (match[i].rm_so>=0) {
      string[match[i].rm_eo]=0;
      ((char**)result)[i]=string+match[i].rm_so;
#ifdef DEBUG
      printf("%s\n",string+match[i].rm_so);
#endif                                                                                                                                                                                                                                                   
    } else {                             
      ((char**)result)[i]="";            
    }
  }

  result[0]=string;                      

  return result;                         

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接