PCREDEMO(3)

πŸ“¦
version 8.45 Β· 2 versions
, in alpine 3.22, alpine 3.23, alpine 3.24, arch rolling, fedora 43, fedora 44, nixos 26.05, opensuse-leap 16.0

πŸ’– pcredemo(3) πŸ’–

okay so bestie ✨, lemme introduce you to <hotpink>pcredemo(3)</hotpink> β€” the PCRE DEMONSTRATION PROGRAM. It's literally a lil demo showing you the most straightforward ways of calling the PCRE regular expression library from a C program. πŸ’…

And if you want a short discussion about it, go peep the pcresample documentation β€” that's <cyan>man pcresample</cyan> if you've got the PCRE man pages installed. 🫢


🌈 Compiling it (Unix-like besties)

okay so in Unix-like environments, if PCRE is installed in your standard system libraries, you should be able to compile this program using this command:

gcc -Wall pcredemo.c -lpcre -o pcredemo

If PCRE is not installed in a standard place, it's likely to be installed with support for the pkg-config mechanism. If you have pkg-config, you can compile this program using this command:

gcc -Wall pcredemo.c 'pkg-config --cflags --libs libpcre' -o pcredemo

If you do not have pkg-config, you may have to use this:

gcc -Wall pcredemo.c -I/usr/local/include -L/usr/local/lib \
  -R/usr/local/lib -lpcre -o pcredemo

Replace "/usr/local/include" and "/usr/local/lib" with wherever the include and library files for PCRE are installed on your system. And heads up bestie 🫢 β€” only some operating systems (e.g. Solaris) use the -R option. πŸ‘‘


πŸ¦‹ Building under Windows

If you want to statically link this program against a non-dll .a file, you must define PCRE_STATIC before including pcre.h, otherwise the pcre_malloc() and pcre_free() exported functions will be declared __declspec(dllimport), with unwanted results. So in this environment, uncomment the following line. πŸ’€

/* #define PCRE_STATIC */

πŸŽ€ The code itself (main character energy)

okay so here's the program. Watch and learn. πŸ’…

#include <stdio.h>
#include <string.h>
#include <pcre.h>

#define OVECCOUNT 30    /* should be a multiple of 3 */

int main(int argc, char **argv)
{
pcre *re;
const char *error;
char *pattern;
char *subject;
unsigned char *name_table;
unsigned int option_bits;
int erroffset;
int find_all;
int crlf_is_newline;
int namecount;
int name_entry_size;
int ovector[OVECCOUNT];
int subject_length;
int rc, i;
int utf8;

πŸ“ First, sort out the command line

okay so first things first β€” deal with the command line. There's only one possible option at the moment: -g, which requests repeated matching to find all occurrences, just like Perl's /g option. We set the variable find_all to a non-zero value if the -g option is present. Apart from that? There must be exactly two arguments. ✨

find_all = 0;
for (i = 1; i < argc; i++)
  {
  if (strcmp(argv[i], "-g") == 0) find_all = 1;
    else break;
  }

After the options, we require exactly two arguments, which are the pattern and the subject string. 🫢

if (argc - i != 2)
  {
  printf("Two arguments required: a regex and a subject string\n");
  return 1;
  }

pattern = argv[i];
subject = argv[i+1];
subject_length = (int)strlen(subject);

🌸 Now we compile the regex

okay now we're gonna compile the regular expression pattern, and handle any errors that get detected. πŸ’…

re = pcre_compile(
  pattern,              /* the pattern */
  0,                    /* default options */
  &error,               /* for error message */
  &erroffset,           /* for error offset */
  NULL);                /* use default character tables */

If compilation failed, print the error message and exit. 😭

if (re == NULL)
  {
  printf("PCRE compilation failed at offset %d: %s\n", erroffset, error);
  return 1;
  }

πŸ¦‹ If the compilation succeeded…

If the compilation succeeded, we call PCRE again, in order to do a pattern match against the subject string. This does just ONE match. If further matching is needed, it will be done below. ✨

rc = pcre_exec(
  re,                   /* the compiled pattern */
  NULL,                 /* no extra data - we didn't study the pattern */
  subject,              /* the subject string */
  subject_length,       /* the length of the subject */
  0,                    /* start at offset 0 in the subject */
  0,                    /* default options */
  ovector,              /* output vector for substring information */
  OVECCOUNT);           /* number of elements in the output vector */

Matching failed? Handle those error cases, bestie. πŸ’€

if (rc < 0)
  {
  switch(rc)
    {
    case PCRE_ERROR_NOMATCH: printf("No match\n"); break;
    /*
    Handle other special cases if you like
    */
    default: printf("Matching error %d\n", rc); break;
    }
  pcre_free(re);     /* Release memory used for the compiled pattern */
  return 1;
  }

Match succeeded!! πŸŽ‰

printf("\nMatch succeeded at offset %d\n", ovector[0]);

πŸ‘‘ We found the first match

We have found the first match within the subject string. If the output vector wasn't big enough, say so. Then output any substrings that were captured. 🫢

The output vector wasn't big enough:

if (rc == 0)
  {
  rc = OVECCOUNT/3;
  printf("ovector only has room for %d captured substrings\n", rc - 1);
  }

Show substrings stored in the output vector by number. Obviously, in a real application you might want to do things other than print them. πŸ’…

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

🧚 Named substrings, because we're fancy

That concludes the basic part of this demonstration program. We have compiled a pattern, and performed a single match. The code that follows shows first how to access named substrings, and then how to code for repeated matches on the same subject. ✨

See if there are any named substrings, and if so, show them by name. First we have to extract the count of named parentheses from the pattern. πŸŽ€

(void)pcre_fullinfo(
  re,                   /* the compiled pattern */
  NULL,                 /* no extra data - we didn't study the pattern */
  PCRE_INFO_NAMECOUNT,  /* number of named substrings */
  &namecount);          /* where to put the answer */
if (namecount <= 0) printf("No named substrings\n"); else
  {
  unsigned char *tabptr;
  printf("Named substrings\n");

Before we can access the substrings, we must extract the table for translating names to numbers, and the size of each entry in the table. πŸ‘‘

  (void)pcre_fullinfo(
    re,                       /* the compiled pattern */
    NULL,                     /* no extra data - we didn't study the pattern */
    PCRE_INFO_NAMETABLE,      /* address of the table */
    &name_table);             /* where to put the answer */

  (void)pcre_fullinfo(
    re,                       /* the compiled pattern */
    NULL,                     /* no extra data - we didn't study the pattern */
    PCRE_INFO_NAMEENTRYSIZE,  /* size of each entry in the table */
    &name_entry_size);        /* where to put the answer */

Now we can scan the table and, for each entry, print the number, the name, and the substring itself. πŸ’…

  tabptr = name_table;
  for (i = 0; i < namecount; i++)
    {
    int n = (tabptr[0] << 8) | tabptr[1];
    printf("(%d) %*s: %.*s\n", n, name_entry_size - 3, tabptr + 2,
      ovector[2*n+1] - ovector[2*n], subject + ovector[2*n]);
    tabptr += name_entry_size;
    }
  }

πŸ”₯ The -g option and repeated matching

If the -g option was given on the command line, we want to continue to search for additional matches in the subject string, in a similar way to the /g option in Perl. And ngl bestie, this turns out to be trickier than you might think because of the possibility of matching an empty string. Here's what happens:

  • If the previous match was NOT for an empty string, we can just start the next match at the end of the previous one. 🫢
  • If the previous match WAS for an empty string, we can't do that, as it would lead to an infinite loop. Instead, a special call of pcre_exec() is made with the PCRE_NOTEMPTY_ATSTART and PCRE_ANCHORED flags set. 😭
    • The first of these tells PCRE that an empty string at the start of the subject is not a valid match; other possibilities must be tried.
    • The second flag restricts PCRE to one match attempt at the initial string position.
    • If this match succeeds, an alternative to the empty string match has been found, and we can print it and proceed round the loop, advancing by the length of whatever was found.
    • If this match does not succeed, we still stay in the loop, advancing by just one character. In UTF-8 mode, which can be set by (*UTF8) in the pattern, this may be more than one byte. 🌸

However, there is a complication concerned with newlines. When the newline convention is such that CRLF is a valid newline, we must advance by two characters rather than one. The newline convention can be set in the regex by (*CR), etc.; if not, we must find the default. πŸ’€

if (!find_all)     /* Check for -g */
  {
  pcre_free(re);   /* Release the memory used for the compiled pattern */
  return 0;        /* Finish unless -g was given */
  }

Before running the loop, check for UTF-8 and whether CRLF is a valid newline sequence. First, find the options with which the regex was compiled; extract the UTF-8 state, and mask off all but the newline options. ✨

(void)pcre_fullinfo(re, NULL, PCRE_INFO_OPTIONS, &option_bits);
utf8 = option_bits & PCRE_UTF8;
option_bits &= PCRE_NEWLINE_CR|PCRE_NEWLINE_LF|PCRE_NEWLINE_CRLF|
               PCRE_NEWLINE_ANY|PCRE_NEWLINE_ANYCRLF;

If no newline options were set, find the default newline convention from the build configuration. 🫢

if (option_bits == 0)
  {
  int d;
  (void)pcre_config(PCRE_CONFIG_NEWLINE, &d);
  /* Note that these values are always the ASCII ones, even in
  EBCDIC environments. CR = 13, NL = 10. */
  option_bits = (d == 13)? PCRE_NEWLINE_CR :
          (d == 10)? PCRE_NEWLINE_LF :
          (d == (13<<8 | 10))? PCRE_NEWLINE_CRLF :
          (d == -2)? PCRE_NEWLINE_ANYCRLF :
          (d == -1)? PCRE_NEWLINE_ANY : 0;
  }

And see if CRLF is a valid newline sequence. πŸ‘‘

crlf_is_newline =
     option_bits == PCRE_NEWLINE_ANY ||
     option_bits == PCRE_NEWLINE_CRLF ||
     option_bits == PCRE_NEWLINE_ANYCRLF;

Loop for second and subsequent matches… πŸŽ€

for (;;)
  {
  int options = 0;                 /* Normally no options */
  int start_offset = ovector[1];   /* Start at end of previous match */

  /* If the previous match was for an empty string, we are finished if we are
  at the end of the subject. Otherwise, arrange to run another match at the
  same point to see if a non‐empty match can be found. */

  if (ovector[0] == ovector[1])
    {
    if (ovector[0] == subject_length) break;
    options = PCRE_NOTEMPTY_ATSTART | PCRE_ANCHORED;
    }

  /* Run the next matching operation */

  rc = pcre_exec(
    re,                   /* the compiled pattern */
    NULL,                 /* no extra data ‐ we didn’t study the pattern */
    subject,              /* the subject string */
    subject_length,       /* the length of the subject */
    start_offset,         /* starting offset in the subject */
    options,              /* options */
    ovector,              /* output vector for substring information */
    OVECCOUNT);           /* number of elements in the output vector */

okay so bestie, we're inside the big loop now πŸ’« β€” the one that keeps going for second, third, fourth… matches until there's literally nothing left to find. Every single spin round this loop starts with options = 0 (no special flags, we're chill) and start_offset = ovector[1], which is just "start looking right after wherever the last match ended." Very demure, very mindful.

But wait, there's a lil trap πŸ™ˆ if the previous match was an empty string β€” like ovector[0] == ovector[1], zero width, nothing there β€” then two things can happen:

  • If we're already at the end of the subject (ovector[0] == subject_length), we're done, break, bye πŸ‘‹
  • Otherwise we set options = PCRE_NOTEMPTY_ATSTART | PCRE_ANCHORED, which basically tells PCRE "okay run it again at this exact same spot but this time actually require something non-empty to show up." Main character problem-solving honestly.

Then pcre_exec() runs again. Same eight arguments as before, just with the new start_offset and options. Note the NULL for the second arg β€” that's the extra data slot, and it's NULL because we didn't study the pattern 😭 no pcre_study() in this demo, we're keeping it simple.


  /* This time, a result of NOMATCH isn’t an error. If the value in "options"
  is zero, it just means we have found all possible matches, so the loop ends.
  Otherwise, it means we have failed to find a non‐empty‐string match at a
  point where there was a previous empty‐string match. In this case, we do what
  Perl does: advance the matching position by one character, and continue. We
  do this by setting the "end of previous match" offset, because that is picked
  up at the top of the loop as the point at which to start again.

  There are two complications: (a) When CRLF is a valid newline sequence, and
  the current position is just before it, advance by an extra byte. (b)
  Otherwise we must ensure that we skip an entire UTF‐8 character if we are in
  UTF‐8 mode. */

  if (rc == PCRE_ERROR_NOMATCH)
    {
    if (options == 0) break;                    /* All matches found */
    ovector[1] = start_offset + 1;              /* Advance one byte */
    if (crlf_is_newline &&                      /* If CRLF is newline & */
        start_offset < subject_length ‐ 1 &&    /* we are at CRLF, */
        subject[start_offset] == ’\r’ &&
        subject[start_offset + 1] == ’\n’)
      ovector[1] += 1;                          /* Advance by one more. */
    else if (utf8)                              /* Otherwise, ensure we */
      {                                         /* advance a whole UTF‐8 */
      while (ovector[1] < subject_length)       /* character. */
        {
        if ((subject[ovector[1]] & 0xc0) != 0x80) break;
        ovector[1] += 1;
        }
      }
    continue;    /* Go round the loop again */
    }

THIS is the bit that eats people alive, no cap 😀 a PCRE_ERROR_NOMATCH here is not an error. Let me say it louder for the people in the back:

  • If options == 0, NOMATCH just means we've found every possible match and the loop is over. break. Periodt. ✨
  • If options isn't zero, it means we tried for a non-empty match at a spot where an empty match already happened and there's nothing there. So we do the Perl thing: shove the matching position forward one character and keep going. And we do that by writing into ovector[1], because that value gets picked up at the top of the loop as the new start. Sneaky but iconic πŸ’…

Two whole complications though, because of course 🫠

  1. When CRLF is a valid newline sequence and we're sitting right before it, we advance by an extra byte β€” so ovector[1] += 1 on top of the + 1 we already did. That's the crlf_is_newline check, guarded by start_offset < subject_length ‐ 1 so we don't read off the end, then peeking at subject[start_offset] and subject[start_offset + 1] for '\r' and '\n'.
  2. Otherwise, if we're in UTF-8 mode (utf8 is true), we can't just skip one byte β€” that could land us in the middle of a character πŸ’€ so the while loop walks forward until it hits a byte where (subject[ovector[1]] & 0xc0) != 0x80, i.e. not a UTF-8 continuation byte. Whole character skipped, clean, slay.

Then continue; and round we go again πŸ”


  /* Other matching errors are not recoverable. */

  if (rc < 0)
    {
    printf("Matching error %d\n", rc);
    pcre_free(re);    /* Release memory used for the compiled pattern */
    return 1;
    }

  /* Match succeeded */

  printf("\nMatch succeeded again at offset %d\n", ovector[0]);

  /* The match succeeded, but the output vector wasn’t big enough. */

  if (rc == 0)
    {
    rc = OVECCOUNT/3;
    printf("ovector only has room for %d captured substrings\n", rc ‐ 1);
    }

Any other error and we are NOT recovering, bestie, we're out πŸšͺ it prints Matching error %d\n with the rc, frees the compiled pattern with pcre_free(re), and return 1s out of the whole program.

If we got here instead, it worked: Match succeeded again at offset %d\n with ovector[0] πŸ’–

And if rc == 0? That's the "match happened but the output vector was too small" situation β€” so it reassigns rc = OVECCOUNT/3 and prints ovector only has room for %d captured substrings\n with rc ‐ 1. (Remember OVECCOUNT/3 because each captured substring eats three slots of the vector. Math girlies stay winning πŸ“)


  /* As before, show substrings stored in the output vector by number, and then
  also any named substrings. */

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

  if (namecount <= 0) printf("No named substrings\n"); else
    {
    unsigned char *tabptr = name_table;
    printf("Named substrings\n");
    for (i = 0; i < namecount; i++)
      {
      int n = (tabptr[0] << 8) | tabptr[1];
      printf("(%d) %*s: %.*s\n", n, name_entry_size ‐ 3, tabptr + 2,
        ovector[2*n+1] ‐ ovector[2*n], subject + ovector[2*n]);
      tabptr += name_entry_size;
      }
    }
  }      /* End of loop to find second and subsequent matches */

Same drill as the first match, but again πŸ” substrings by number: substring_start is subject + ovector[2*i], substring_length is ovector[2*i+1] ‐ ovector[2*i], and it prints "%2d: %.*s\n" with i. The %.*s is doing the lord's work β€” it takes the length as an argument so you don't need a null-terminated copy. Obsessed 😌

Then the named ones. If namecount <= 0 it just says No named substrings\n and moves on. Otherwise: printf("Named substrings\n") and we walk tabptr through name_table, name_entry_size bytes at a time πŸšΆβ™€οΈ

The number for each name is stashed in the first two bytes: int n = (tabptr[0] << 8) | tabptr[1]; β€” big-endian style, very retro, very cute. Then it prints "(%d) %*s: %.*s\n" with:

  • n β€” the subpattern number
  • name_entry_size ‐ 3 β€” the field width for the name (the ‐ 3 accounts for the two length bytes plus the terminating zero)
  • tabptr + 2 β€” where the name string actually starts
  • ovector[2*n+1] ‐ ovector[2*n] β€” the length of the captured substring
  • subject + ovector[2*n] β€” where that substring starts

Then tabptr += name_entry_size; and next name please πŸ’«

And that closing brace is the end of the loop that finds second and subsequent matches. Round of applause honestly πŸ‘


printf("\n");
pcre_free(re);       /* Release memory used for the compiled pattern */
return 0;
}

/* End of pcredemo.c */

A polite little printf("\n") for spacing, pcre_free(re) to hand back the memory the compiled pattern was using (yes, you have to do this yourself, there's no garbage collector here, it's giving ✨manual memory management✨), return 0 meaning "all good, we succeeded," and that's a wrap on main(). πŸŽ€

/* End of pcredemo.c */ β€” the end of the file, and the end of the page, bestie πŸ¦‹


heads up: if you were hoping this page would tell you what every PCRE option does, it doesn't β€” this is a demo program, not a reference. The whole point is showing you the shape of a real C program calling PCRE. For the actual full rundown, go read the other pages in SEE ALSO. iykyk πŸ’…

And that's the whole thing!! The very last line of the page is its footer β€” the source, the version, and the date β€” which is just the manpage's way of saying "here's where I came from and when I was last edited." So: shoutout to the source, shoutout to the version, shoutout to the date, and shoutout to you for making it all the way through a 300-line C program in a manpage 🫢 you ate. fr fr. πŸŒˆβ­πŸ’–