Регулярные выражения в C: примеры?

Я привожу несколько простых примеров и лучшие практики использования регулярных выражений в ANSI C. man regex.h не предоставляет такой большой помощи.

6 ответов

Решение

Регулярные выражения на самом деле не являются частью ANSI C. Возможно, вы говорите о библиотеке регулярных выражений POSIX, которая поставляется с большинством (все?) * Nixes. Вот пример использования регулярных выражений POSIX в C (на основе этого):

#include <regex.h>        
regex_t regex;
int reti;
char msgbuf[100];

/* Compile regular expression */
reti = regcomp(&regex, "^a[[:alnum:]]", 0);
if (reti) {
    fprintf(stderr, "Could not compile regex\n");
    exit(1);
}

/* Execute regular expression */
reti = regexec(&regex, "abc", 0, NULL, 0);
if (!reti) {
    puts("Match");
}
else if (reti == REG_NOMATCH) {
    puts("No match");
}
else {
    regerror(reti, &regex, msgbuf, sizeof(msgbuf));
    fprintf(stderr, "Regex match failed: %s\n", msgbuf);
    exit(1);
}

/* Free memory allocated to the pattern buffer by regcomp() */
regfree(&regex);

В качестве альтернативы вы можете попробовать PCRE, библиотеку для Perl-совместимых регулярных выражений на C. Синтаксис Perl во многом аналогичен синтаксису, используемому в Java, Python и ряде других языков. Синтаксис POSIX - это синтаксис, используемый grep, sed, vi, так далее.

Это пример использования REG_EXTENDED. Это регулярное выражение

"^(-)?([0-9]+)((,|.)([0-9]+))?\n$"

Позволяет ловить десятичные числа в испанской и международной системе.:)

#include <regex.h>
#include <stdlib.h>
#include <stdio.h>
regex_t regex;
int reti;
char msgbuf[100];

int main(int argc, char const *argv[])
{
    while(1){
        fgets( msgbuf, 100, stdin );
        reti = regcomp(&regex, "^(-)?([0-9]+)((,|.)([0-9]+))?\n$", REG_EXTENDED);
        if (reti) {
            fprintf(stderr, "Could not compile regex\n");
            exit(1);
        }

        /* Execute regular expression */
        printf("%s\n", msgbuf);
        reti = regexec(&regex, msgbuf, 0, NULL, 0);
        if (!reti) {
            puts("Match");
        }
        else if (reti == REG_NOMATCH) {
            puts("No match");
        }
        else {
            regerror(reti, &regex, msgbuf, sizeof(msgbuf));
            fprintf(stderr, "Regex match failed: %s\n", msgbuf);
            exit(1);
        }

        /* Free memory allocated to the pattern buffer by regcomp() */
        regfree(&regex);
    }

}

man regex.h сообщает, что нет никакого ручного ввода для regex.h, но man 3 regex дает вам страницу, объясняющую функции POSIX для сопоставления с образцом.

Те же функции описаны в библиотеке GNU C: сопоставление регулярных выражений, где объясняется, что библиотека GNU C поддерживает как интерфейс POSIX.2, так и тот, который библиотека GNU C имела в течение многих лет.

Например, для гипотетической программы, которая печатает, какая из строк, переданных в качестве аргумента, соответствует шаблону, переданному в качестве первого аргумента, вы можете использовать код, подобный следующему:

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

void print_regerror (int errcode, size_t length, regex_t *compiled);

int
main (int argc, char *argv[])
{
  regex_t regex;
  int result;

  if (argc < 3)
    {
      // The number of passed arguments is lower than the number of
      // expected arguments.
      fputs ("Missing command line arguments\n", stderr);
      return EXIT_FAILURE;
    }

  result = regcomp (&regex, argv[1], REG_EXTENDED);
  if (result)
    {
      // Any value different from 0 means it was not possible to 
      // compile the regular expression, either for memory problems
      // or problems with the regular expression syntax.
      if (result == REG_ESPACE)
        fprintf (stderr, "%s\n", strerror(ENOMEM));
      else
        fputs ("Syntax error in the regular expression passed as first argument\n", stderr);
      return EXIT_FAILURE;               
    }
  for (int i = 2; i < argc; i++)
    {
      result = regexec (&regex, argv[i], 0, NULL, 0);
      if (!result)
        {
          printf ("'%s' matches the regular expression\n", argv[i]);
        }
      else if (result == REG_NOMATCH)
        {
          printf ("'%s' doesn't the regular expression\n", argv[i]);
        }
      else
        {
          // The function returned an error; print the string 
          // describing it.
          // Get the size of the buffer required for the error message.
          size_t length = regerror (result, &regex, NULL, 0);
          print_regerror (result, length, &regex);       
          return EXIT_FAILURE;
        }
    }

  /* Free the memory allocated from regcomp(). */
  regfree (&regex);
  return EXIT_SUCCESS;
}

void
print_regerror (int errcode, size_t length, regex_t *compiled)
{
  char buffer[length];
  (void) regerror (errcode, compiled, buffer, length);
  fprintf(stderr, "Regex match failed: %s\n", buffer);
}

Последний аргумент regcomp() должен быть хотя бы REG_EXTENDED или функции будут использовать базовые регулярные выражения, что означает, что (например) вам нужно будет использовать a\{3\} вместо a{3} используется из расширенных регулярных выражений, что, вероятно, и ожидается.

POSIX.2 также имеет другую функцию для сопоставления с подстановочными знаками: fnmatch(), Он не позволяет скомпилировать регулярное выражение или получить подстроки, соответствующие подвыражению, но он очень специфичен для проверки совпадения имени файла с подстановочным знаком (например, он использует FNM_PATHNAME флаг).

Это, вероятно, не то, что вам нужно, но такой инструмент, как re2c, может компилировать регулярные выражения POSIX(-ish) в ANSI C. Он написан как замена lex, но этот подход позволяет вам пожертвовать гибкостью и удобочитаемостью ради последней скорости, если вам это действительно нужно.

Хотя приведенный выше ответ хороший, я рекомендую использовать PCRE2. Это означает, что вы можете буквально использовать все примеры регулярных выражений, и вам не нужно переводить их с какого-то древнего регулярного выражения.

Я уже ответил на это, но думаю, что и здесь это может помочь.

Regex In C для поиска номеров кредитных карт

// YOU MUST SPECIFY THE UNIT WIDTH BEFORE THE INCLUDE OF THE pcre.h

#define PCRE2_CODE_UNIT_WIDTH 8
#include <stdio.h>
#include <string.h>
#include <pcre2.h>
#include <stdbool.h>

int main(){

bool Debug = true;
bool Found = false;
pcre2_code *re;
PCRE2_SPTR pattern;
PCRE2_SPTR subject;
int errornumber;
int i;
int rc;
PCRE2_SIZE erroroffset;
PCRE2_SIZE *ovector;
size_t subject_length;
pcre2_match_data *match_data;


char * RegexStr = "(?:\\D|^)(5[1-5][0-9]{2}(?:\\ |\\-|)[0-9]{4}(?:\\ |\\-|)[0-9]{4}(?:\\ |\\-|)[0-9]{4})(?:\\D|$)";
char * source = "5111 2222 3333 4444";

pattern = (PCRE2_SPTR)RegexStr;// <<<<< This is where you pass your REGEX 
subject = (PCRE2_SPTR)source;// <<<<< This is where you pass your bufer that will be checked. 
subject_length = strlen((char *)subject);




  re = pcre2_compile(
  pattern,               /* the pattern */
  PCRE2_ZERO_TERMINATED, /* indicates pattern is zero-terminated */
  0,                     /* default options */
  &errornumber,          /* for error number */
  &erroroffset,          /* for error offset */
  NULL);                 /* use default compile context */

/* Compilation failed: print the error message and exit. */
if (re == NULL)
  {
  PCRE2_UCHAR buffer[256];
  pcre2_get_error_message(errornumber, buffer, sizeof(buffer));
  printf("PCRE2 compilation failed at offset %d: %s\n", (int)erroroffset,buffer);
  return 1;
  }


match_data = pcre2_match_data_create_from_pattern(re, NULL);

rc = pcre2_match(
  re,
  subject,              /* the subject string */
  subject_length,       /* the length of the subject */
  0,                    /* start at offset 0 in the subject */
  0,                    /* default options */
  match_data,           /* block for storing the result */
  NULL);

if (rc < 0)
  {
  switch(rc)
    {
    case PCRE2_ERROR_NOMATCH: //printf("No match\n"); //
    pcre2_match_data_free(match_data);
    pcre2_code_free(re);
    Found = 0;
    return Found;
    //  break;
    /*
    Handle other special cases if you like
    */
    default: printf("Matching error %d\n", rc); //break;
    }
  pcre2_match_data_free(match_data);   /* Release memory used for the match */
  pcre2_code_free(re);
  Found = 0;                /* data and the compiled pattern. */
  return Found;
  }


if (Debug){
ovector = pcre2_get_ovector_pointer(match_data);
printf("Match succeeded at offset %d\n", (int)ovector[0]);

if (rc == 0)
  printf("ovector was not big enough for all the captured substrings\n");


if (ovector[0] > ovector[1])
  {
  printf("\\K was used in an assertion to set the match start after its end.\n"
    "From end to start the match was: %.*s\n", (int)(ovector[0] - ovector[1]),
      (char *)(subject + ovector[1]));
  printf("Run abandoned\n");
  pcre2_match_data_free(match_data);
  pcre2_code_free(re);
  return 0;
}

for (i = 0; i < rc; i++)
  {
  PCRE2_SPTR substring_start = subject + ovector[2*i];
  size_t substring_length = ovector[2*i+1] - ovector[2*i];
  printf("%2d: %.*s\n", i, (int)substring_length, (char *)substring_start);
  }
}

else{
  if(rc > 0){
    Found = true;

    } 
} 
pcre2_match_data_free(match_data);
pcre2_code_free(re);
return Found;

}

Установите PCRE, используя:

wget https://ftp.pcre.org/pub/pcre/pcre2-10.31.zip
make 
sudo make install 
sudo ldconfig

Компиляция с использованием:

gcc foo.c -lpcre2-8 -o foo

Проверьте мой ответ для более подробной информации.

Этот пример соответствует странице руководства, существующей в Ubuntu 22.04.3 LTS.

В исходном коде представлены базовые и расширенные регулярные выражения POSIX .

Семантика в чем-то аналогична использованиюre.compileв Python — есть вызов для компиляции регулярного выражения и вызов для сопоставления его с целевой строкой . Функция, доступная для компиляции регулярного выражения:

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

Первый параметр,*preg, является указателем типаregex_t. В конечном итоге это определяетсяre_pattern_bufferв исходнике и являетсяstructчлены которого будут содержать сведения, связанные с регулярным выражением, такие как количество байтов, выделенных для регулярного выражения, флаги, указывающие, существуют ли в регулярном выражении подвыражения и т. д. Здесь находится все состояние скомпилированного регулярного выражения. В документации это называется областью хранения буфера шаблонов.

Второй параметр,*regex, указывает на строку регулярного выражения. Это шаблон регулярного выражения POSIX, который используется для поиска по целевой строке.

Третий параметр,cflags, или побитовое или из следующих флагов:

  • REG_EXTENDED , т.е. использовать расширенные регулярные выражения POSIX.
  • REG_ICASE , то есть использовать сопоставление шаблонов без учета регистра.
  • REG_NOSUB , т.е. не сообщать о положении совпадений (подробнее ниже)
  • REG_NEWLINE , т.е. не позволяйте подстановочным знакам соответствовать символам новой строки.

Вот скомпилированное регулярное выражение:

      // holds details about the regular expression
static regex_t preg;

// the raw regular expression (static'd ref and val)
static const char *const regex = "^bork.*$";

// pack details needed for matching into preg
// then return 0 if compiled correctly, else error code
int errorcode = regcomp(&preg, regex, REG_ICASE);

Регулярное выражение^bork.*$теперь представлен/определен членамиpreg. REG_ICASEфлаг также был передан, так что целевая строка со смешанным регистром не будет иметь значения.

Компиляция регулярного выражения может пойти не так. Как минимум 14 различными способами на основе страницы руководства. Чтобы справиться с этим,regcompвозвращает0об успехе или коде ошибки, когда что-то идет не так.

Каждый код ошибки соответствует текстовому сообщению об ошибке, а также имеет функцию, которая принимает код ошибки и записывает это текстовое сообщение в нужный буфер:

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

Параметры этой функции по порядку:

  • наш код ошибки,errorcode
  • указатель на скомпилированное регулярное выражение,&preg
  • указатель на некоторый буфер, в который можно записать сообщение
  • размер буфера

Один простой шаблон, который реализуетregerrorэто:

      // initialize regular expression buffer struct
regex_t preg;

// initialize bad regular expression string
static const char *const regex = "[A-Z";

// initialize buffer for error message
int ebuffsize = 1000;
char ebuff[ebuffsize];

// compile with no flags
int ecode = regcomp(&preg, regex, 0);

// initialize and get textual error length
int etextsize;
if (ecode)
    etextsize = regerror(ecode, &preg, ebuff, ebuffsize);
    // use etextsize to resize ebuff if its too small ...

Чтобы фактически выполнить сопоставление с скомпилированным регулярным выражением, предоставляет

      int regexec(
    const regex_t *preg, // pointer to pattern buffer storage area
    const char *string,  // pointer to target string
    size_t nmatch,       // number of entries in pmatch
    regmatch_t pmatch[], // array of regmatch_t types 
    int eflags           // bitwise-or match options
);

Простейшее использование происходит, когда регулярное выражение компилируется с помощьюREG_NOSUBфлаг, так как это будет учитывать значимостьnmatchиpmatch(эти два параметра участвуют в сопоставлении адресации, описанном ниже). Вот этот простой, но надуманный вариант использования:

      // initialize regular expression buffer struct
regex_t preg;

// initialize regular expression string
static const char *const regex = "bork";

// compile regular expression or exit
int ecode = regcomp(&preg, regex, REG_NOSUB);
if (ecode)
    exit(-1);

// perform matching and print output
static const char *const target = "spoon-bork-knife";
printf("match value: %i", regexec(&preg, target, 0, NULL, 0));

// prints 0 to indicate success

Использование совпадающей адресации немного сложнее. Адресация по совпадению позволяет извлекать совпадающие шаблоны из целевой строки, как в примере на странице руководства (с добавленными комментариями):

      #include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <regex.h>

// macro to determine nmatch from pmatch
#define ARRAY_SIZE(arr) (sizeof((arr)) / sizeof((arr)[0]))

// initialize target string and regular expression string
static const char *const str = "1) John Driverhacker;\n2) John Doe;\n3) John Foo;\n";
static const char *const re = "John.*o";

int main(void)
{
    // pointer to target string and regular expression
    static const char *s = str;
    regex_t     regex;
    
    // initialize pmatch containter for caching match offest and length
    regmatch_t  pmatch[1];
    
    // initialize offset and length types
    regoff_t    off, len;
    
    // exit if regular expression cant be compiled
    if (regcomp(&regex, re, REG_NEWLINE))
        exit(EXIT_FAILURE);

    printf("String = \"%s\"\n", str);
    printf("Matches:\n");
    
    // for each possible match
    for (int i = 0; ; i++) {
        
        // exit if no more matches
        if (regexec(&regex, s, ARRAY_SIZE(pmatch), pmatch, 0))
            break;
        
        // compute offset of match and length of match and print
        off = pmatch[0].rm_so + (s - str);
        len = pmatch[0].rm_eo - pmatch[0].rm_so;
        printf("#%d:\n", i);
        printf("offset = %jd; length = %jd\n", (intmax_t) off, (intmax_t) len);
        
        // print the match
        printf("substring = \"%.*s\"\n", len, s + pmatch[0].rm_so);
        
        // move the pointer to the next start of the string
        s += pmatch[0].rm_eo;
    }

    exit(EXIT_SUCCESS);
}

Вот результат:

      String = "1) John Driverhacker;
2) John Doe;
3) John Foo;
"
Matches:
#0:
offset = 25; length = 7
substring = "John Do"
#1:
offset = 38; length = 8
substring = "John Foo"

Последняя функция вregex.hследует упомянуть

      void regfree(regex_t *preg);

Этой функции передается буфер регулярных выражений, и она аналогичнаfreeв плане гигиены.

Другие вопросы по тегам