PCREDEMO(3)
π 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 thePCRE_NOTEMPTY_ATSTARTandPCRE_ANCHOREDflags 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
optionsisn'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 intoovector[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 π«
- When CRLF is a valid newline sequence and we're sitting right before it, we advance by an extra byte β so
ovector[1] += 1on top of the+ 1we already did. That's thecrlf_is_newlinecheck, guarded bystart_offset < subject_length β 1so we don't read off the end, then peeking atsubject[start_offset]andsubject[start_offset + 1]for'\r'and'\n'. - Otherwise, if we're in UTF-8 mode (
utf8is true), we can't just skip one byte β that could land us in the middle of a character π so thewhileloop 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 numbername_entry_size β 3β the field width for the name (theβ 3accounts for the two length bytes plus the terminating zero)tabptr + 2β where the name string actually startsovector[2*n+1] β ovector[2*n]β the length of the captured substringsubject + 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. πβπ
π¦ where this page comes from
- alpine 3.22: pcre-doc 8.45-r4 Β· BSD-3-Clause
- alpine 3.23: pcre-doc 8.45-r4 Β· BSD-3-Clause
- alpine 3.24: pcre-doc 8.45-r4 Β· BSD-3-Clause
- arch rolling: pcre 8.45-5 Β· BSD
- fedora 43: pcre-devel 8.45-1.fc43.9 Β· BSD-3-Clause
- fedora 43: pcre-devel 8.45-1.fc43.9 Β· BSD-3-Clause
- fedora 44: pcre-devel 8.45-1.fc43.9 Β· BSD-3-Clause
- fedora 44: pcre-devel 8.45-1.fc43.9 Β· BSD-3-Clause
- nixos 26.05: pcre 8.45
- nixos 26.05: pcre-cpp 8.45
- opensuse-leap 16.0: pcre-devel 8.45-160000.2.2 Β· BSD-3-Clause
- opensuse-leap 16.0: pcre-devel 8.45-160000.2.2 Β· BSD-3-Clause
- opensuse-leap 16.0: pcre-devel 8.45-160000.2.2 Β· BSD-3-Clause
- opensuse-leap 16.0: pcre-devel 8.45-160000.2.2 Β· BSD-3-Clause
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms