mario::konrad
programming / C++ / sailing / nerd stuff
Regular Expressions in C
© 2005 / Mario Konrad

Einleitung

Regular Expressions (zu deutsch: reguläre Ausdrücke) eignen sich hervorragend zur Mustererkennung (pattern matching).

Die C Standardbibliothek bietet die Möglichkeit nach Mustern zu suchen in einem einfachen String. Das Erstezen von Teilstrings ist nicht möglich.

Dieses Tutorial geht davon aus, dass Programmierkenntnisse und der Umgang mit der Programmiersprache C vorhanden sind.

Pattern Matching

Zuerst wenden wir uns einer einfachen Anwenung zu: dem pattern matching. Das Problem ist: es gibt einen String und dieser soll auf ein Muster geprüft werden. Hier interessiert nur, ob das Muster vorkommt oder nicht. Es sind keine weiteren Informationen von bedeutung (welche Teilstrings dem Muster ähneln).

Der Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <regex.h>

int match(char * string, char * pattern)
{
        int result;
        regex_t reg;
        if (regcomp(&reg, pattern, REG_EXTENDED | REG_NOSUB) != 0) return -1;
        result = regexec(&reg, string, 0, 0, 0);
        regfree(&reg);
        return result;
}

int main(int argc, char ** argv)
{
        if (argc != 3)
        {
                fprintf(stderr, "error: wrong number of parameters\n");
                return -1;
        }
        (match(argv[1], argv[2])) ? printf("NO_MATCH\n") : printf("MATCH\n");
        return 0;
}

Walkthrough

Zuerst ein paar header files:

1
2
#include <stdio.h>
#include <regex.h>

Die Funktion match prüft den angegebenen String (string) auf das spezifizierte Muster (pattern). Wir benötigen zwei lokale Variablen. Eine, die einen Errorcode aufnehmen kann (result) und eine Struktur zur Prüfung des Strings (reg).

4
5
6
7
int match(char * string, char * pattern)
{
        int result;
        regex_t reg;

Zuerst müssen wir die Struktur reg initialisieren. Dies führen wir mit der Funktion regcomp durch. Dieser nimmt einen Pointer auf die Struktur, das Pattern und Flags. Falls die Operation geglückt ist, liefert die Funktion 0. Falls nicht einen Fehlercode ungleicht 0.

8
        if (regcomp(&reg, pattern, REG_EXTENDED | REG_NOSUB) != 0) return -1;

Die eigentliche Operation geschieht nun mit Hilfe der Funktion regexec. In dier hier benutzten Form, liefert sie bloss eine Erfolgsmeldung. Wurde das Muster gefunden, so wird 0 zurückgeliefert, sonst ein Fehlercode.

9
        result = regexec(&reg, string, 0, 0, 0);

Zum Schluss müssen wir den Speicher der Struktur reg wieder freigeben. Dazu die Funktion regfree. Als Rückgabewert unserer Funktion match liefern wir das Resultat von regexec.

10
11
12
        regfree(&reg);
        return result;
}

Die Funktion main sollte dem erfahrenen C Programmierer keinerlei Schwierigkeiten bieten und wird deshalb nicht weiter erläutert.

Anwendung

Das Programm muss zuerst compiliert werden:

$ gcc -o match match.c

Danach kann es mit zwei Parametern gestartet werden:

$ ./match string pattern

Beispiel:

$ ./match "Hello World, this is a test." "W.*ld"

Alle Vorkommnisse

Oft ist es auch nützlich zu wissen wo das Muster überall gefunden wurde im String. Das obige Beispiel reicht hierfür nicht mehr, da dies lediglich auf ein generelles Vorkommen testet. Es gibt aber eine einfache und elegante Möglichkeit diese, evtl. mehreren, Vorkommnisse herauszuholen.

Der Code ====

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <regex.h>

void show(char * string, int start, int end)
{
    int i;
    printf("%d-%d: ", start, end);
    for (i = start; i < end; ++i) putchar(string[i]);
    putchar('\n');
}

void parts(char * string, char * pattern)
{
    int absolute = 0;
    int error;
    regex_t reg;
    regmatch_t pm;

    if (regcomp(&reg, pattern, REG_EXTENDED) != 0) return;
    error = regexec(&reg, string, 1, &pm, 0);
    while (error == 0)
    {
        show(string, absolute+pm.rm_so, absolute+pm.rm_eo);
        absolute += pm.rm_eo;
        error = regexec(&reg, absolute+string, 1, &pm, REG_NOTBOL);
    }
    regfree(&reg);
}

int main(int argc, char ** argv)
{
    if (argc != 3)
    {
        fprintf(stderr, "error: wrong number of parameters\n");
        return -1;
    }
    parts(argv[1], argv[2]);
    return 0;
}

Walkthrough

Zunächst brauchen wir die gewohnten header files:

1
2
#include <stdio.h>
#include <regex.h>

Wir definieren eine Funktion die es uns auf einfache Weise ermöglicht einen Teilstring und dessen Position anzuzeigen:

4
5
6
7
8
9
10
void show(char * string, int start, int end)
{
    int i;
    printf("%d-%d: ", start, end);
    for (i = start; i < end; ++i) putchar(string[i]);
    putchar('\n');
}

Der interessante Teil beginnt hier. Eine Funktion (parts) welche wiederum einen String und ein Muster übernimmt. Diesmal brauchen wir zwei Variablen mehr. Zum einen ist dies absolute, die die augenblickliche Position im String darstellt. Zum andern ist dies pm vom Typ regmatch_t. Diese brauchen wir um auf das Gefundene zu schliessen.

12
13
14
15
16
17
void parts(char * string, char * pattern)
{
    int absolute = 0;
    int error;
    regex_t reg;
    regmatch_t pm;

Die Compilation des Musters geschieht auf die gleiche Weise wie oben.

19
    if (regcomp(&reg, pattern, REG_EXTENDED) != 0) return;

Das Finden des Musters geschieht nun mit zusätzlichen Parametern. Falls das Muster gefunden wurde, steht nun die Positionsinformation in der Struktur pm. Solange nun kein Fehler auftritt und das Muster weiterhin vorhanden ist, versuchen wir im verbleibenden String das Muster erneut zu finden. Zu beachten gibt's noch, dass wenn wir die Variable string bei jedem Durchgang erhöhen würden, dann könnten wir sogar auf die Variable absolute verzichten. Bei jedem Durchgang wird dann der gefundene Teilstring und dessen Position ausgegeben.

20
21
22
23
24
25
26
27
28
    error = regexec(&reg, string, 1, &pm, 0);
    while (error == 0)
    {
        show(string, absolute+pm.rm_so, absolute+pm.rm_eo);
        absolute += pm.rm_eo;
        error = regexec(&reg, absolute+string, 1, &pm, REG_NOTBOL);
    }
    regfree(&reg);
}

Auf weitere Ausführungen der Funktion main wird an dieser Stelle verzichtet.

Nach Hinweis und auf Wunsch von Florian Guist eine kurze Erklärung zur Zeile 25: bei dem Ausdruck absolute+string handelt es sich um die aboslute Position innerhalb des Strings an welchem der nächste regex gesucht werden soll. Man könnte natürlich (als Einstiegshilfe in die Pointer-Arithmetik) auch schreiben: &(string[absolute]) (und nein: Pointer-Arithmetik ist nicht generell schlecht).

Anwendung

Das Programm muss zuerst compiliert werden:

$ gcc -o parts parts.c

Danach kann es mit zwei Parametern gestartet werden:

$ ./parts string pattern

Beispiel:

$ ./parts "hello world, this is a real world test." "world"

Download Source Code

Alle source code Files die auf dieser Seite publiziert werden, sind frei zu kopieren, modifizieren, weiterverbreiten und zu verwenden. Das copyright des Tutorials hat Mario Konrad.

Anhang

Flags für regcomp

Flags für regexec

Fehlercodes

Rererenzen

Die wichtigsten Referenzen sind die man-pages der folgenden Funktionen: