นิพจน์ทั่วไปใน C: ตัวอย่าง


171

ฉันทำตามตัวอย่างง่ายๆและแนวทางปฏิบัติที่ดีที่สุดของวิธีใช้นิพจน์ทั่วไปใน ANSI C. man regex.hไม่ได้ให้ความช่วยเหลือมากนัก


6
ไม่มีการสนับสนุนในตัวสำหรับ regex ใน ANSI C คุณใช้ไลบรารีใดของ regex
Joe

7
Rob Pikeเขียนฟังก์ชั่นการค้นหาสตริงนิพจน์ปกติขนาดเล็กที่ยอมรับชุดย่อยที่มีประโยชน์มากของการแสดงออกปกติสำหรับหนังสือ The Practice of Programming ซึ่งเขาและBrian Kernighanร่วมประพันธ์ ดูการสนทนานี้การจับคู่นิพจน์ทั่วไปโดยดร. Kernighan cs.princeton.edu/courses/archive/spr09/cos333/beautiful.html
Richard Chambers

คำตอบ:


233

จริง ๆ แล้วนิพจน์ปกติไม่ใช่ส่วนหนึ่งของ ANSI C ดูเหมือนคุณอาจกำลังพูดถึงไลบรารีนิพจน์ปกติ POSIX ซึ่งมาพร้อมกับนิพจน์ส่วนใหญ่ (ทั้งหมด?) * นี่คือตัวอย่างของการใช้ POSIX regexes ใน 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ฯลฯ


7
นอกจากว่าคุณต้องหลีกเลี่ยงการพึ่งพา I PCRE ที่สองมันมีการปรับปรุงไวยากรณ์ที่ดีและมีเสถียรภาพมาก อย่างน้อยกับ Linux เวอร์ชันเก่าบางรุ่น "นิพจน์ในตัว" ไลบรารีนิพจน์ทั่วไปไม่ยากเกินกว่าที่จะพังเนื่องจากสตริงอินพุตบางรายการและนิพจน์ปกติบางอย่างที่ "เกือบ" ตรงกับหรือเกี่ยวข้องกับอักขระพิเศษจำนวนมาก
bdk

@ Laurence ความหมายของการส่งผ่าน 0 ถึง regcomp คืออะไร? regcomp ใช้ค่าเลขจำนวนเต็มสี่ค่า 1, 2, 4 และ 8 เพื่อแทน 4 โหมดที่แตกต่างกัน
lixiang

2
@lixiang พารามิเตอร์สุดท้ายที่จะregcomp, cflagsเป็น bitmask จากpubs.opengroup.org/onlinepubs/009695399/functions/regcomp.html : "อาร์กิวเมนต์cflagsเป็น bitwise-inclusive OR หรือมีค่าสถานะต่อไปนี้เป็นศูนย์หรือมากกว่า ... " หากคุณเป็นศูนย์หรือรวมกันคุณจะได้รับ 0 ฉันเห็นว่า manpage Linux สำหรับการregcompพูดว่า "cflags อาจเป็นบิตหรือ - หนึ่งหรือมากกว่าต่อไปนี้" ซึ่งดูเหมือนจะทำให้เข้าใจผิด
Laurence Gonsalves

2
คุณสามารถแยกข้อความจากกลุ่มการจับคู่ด้วยสิ่งที่ชอบ: regmatch_t matches[MAX_MATCHES]; if (regexec(&exp, sz, MAX_MATCHES, matches, 0) == 0) { memcpy(buff, sz + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so); printf("group1: %s\n", buff); }โปรดทราบว่าการจับคู่กลุ่มเริ่มต้นที่ 1, กลุ่ม 0 คือสตริงทั้งหมด เพิ่มการตรวจสอบข้อผิดพลาดนอกขอบเขตและอื่น ๆ
BurnsBA

2
เกี่ยวกับว่าregfreeมีความจำเป็นหลังจากเกิดความล้มเหลวหรือไม่regcompแม้ว่าจะมีการระบุไว้ค่อนข้างจริง แต่ก็ไม่แนะนำให้ทำเช่นนี้: redhat.com/archives/libvir-list/2013-September/msg00276.html
Daniel Jour

12

อาจไม่ใช่สิ่งที่คุณต้องการ แต่เครื่องมืออย่างre2cสามารถรวบรวม POSIX (--ish) นิพจน์ปกติไปยัง ANSI C มันถูกเขียนขึ้นเพื่อแทนที่lexแต่วิธีการนี้จะช่วยให้คุณเสียสละความยืดหยุ่นและความชัดเจนสำหรับความเร็วสุดท้ายหาก คุณต้องการมันจริงๆ


9

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แฟล็ก)


6

นี่คือตัวอย่างของการใช้ 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);
    }

}

5

ในขณะที่คำตอบข้างต้นเป็นสิ่งที่ดีผมขอแนะนำให้ใช้PCRE2 นี่หมายความว่าคุณสามารถใช้ตัวอย่าง regex ทั้งหมดได้แล้วโดยไม่ต้องแปลจาก regex โบราณ

ฉันตอบไปแล้ว แต่ฉันคิดว่ามันสามารถช่วยได้ที่นี่เช่นกัน ..

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

ตรวจสอบคำตอบของฉันสำหรับรายละเอียดเพิ่มเติม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.