Comment utiliser regex.h ?

2022-08-19T13:46:36Z

Suite à une idée de solene, j'ai récemment modifié le code de vger afin d'utiliser une expression régulière (regex) afin de récupérer les éléments dans une requête :

Puisque vger est écrit en C, j'ai décidé d'utiliser la bibliothèque présente de base dans OpenBSD (et la plupart des systèmes) : regex.h.

La lecture de la page man regex.3 fut fastidieuse. Elle est pourtant claire, mais je suis de ceux qui ont besoin d'exemples pour bien comprendre, en particulier comment récupérer les chaînes de caractères d'une partie seulement de la regex : les substring.

Voici donc comment utiliser regex.h.

vger

regex.3

La regex utilisée

Imaginons que nous ayons la phrase : "Mon nom est Paul Muad'Dib".

On veut récupérer le prénom et le nom. On pourrait alors utiliser cette regex "^.* (.*) (.*)$"

Les subexpressions à récupérer sont entre parenthèses.

Déclarations de variables

Il nous faut une variable de type regex_t pour compiler la regex.

On enregistrera les subexpressions dans une structure regmatch_t qu'on appelera "match". Cette dernière est un tableau. Elle doit être assez grande pour contenir la ligne complète correspondant à la regex et chaque substring à capturer.

Dans notre cas, ça fait 2 captures + la phrase complète.

J'ai vu dans le code source de ed qu'ils utilisent un #define, ce qui me parait assez malin. (ils définissent la taille à 30 oO).

Cela nous donne :

#define SE_MAX 3 /* number of expected subexpressions + 1 /*
...
regex_t reg;
regmatch_t match[SE_MAX];
size_t nmatch = SE_MAX;

On éxécute la regex

On appelle donc regcomp pour compiler la regex, et regexec pour l'éxécuter (PAN!).

regcomp(&reg, regex, REG_EXTENDED);
regexec(&reg, s, nmatch, match, 0);

On récupère les substrings

Maintenant, match[1] doit contenir le prénom et match[2] le nom.

Ainsi, reg.re_nsub doit être égal à 2.

Chaque élément dans match est constitué de 2 éléments : rm_so et rm_eo. Ils décrivent l'offset (le décalage) où commence et termine une substring dans la phrase de départ.

Dans notre phrase, le prénom commence après avoir déplacé un curseur imaginaire 12 fois depuis le début.

Mon nom est Paul Muad'Dib
            ^   ^
            |   |
	    |   +-> match[1].rm_eo = 16
	    |
	    +-> match[1].rm_so = 12

Si rien n'a été trouvé, rm_so et rm_eo sont égaux.

On vérifie donc tout d'abord qu'on a bien trouvé le prénom :

if ((len = match[i].rm_eo - match[i].rm_so) > 0) {

On en profite pour calculer la longeur de la chaîne à récupérer.

Ensuite, on copie cette substring, située à s + match[i].rm_so, s étant notre phrase de départ.

memcpy(first, s + match[i].rm_so, len);

Le code entier

Ici on rajoute de quoi gérer d'éventuelles erreurs, dont le message est enregistré dans "buf".

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <regex.h>
#include <sys/types.h>
#define SE_MAX 3	/* max subexpression, in nmatch. hard to really understand */
int 
main(int argc, char *argv[])
{
	regex_t reg;
	regmatch_t match[SE_MAX];
	size_t nmatch = SE_MAX;
	size_t len = 0;
	int ret = 0;
	char buf[BUFSIZ] = {'\0'};
	char *regex = "^.* (.*) (.*)$";
	char *s = "My name is Paul Muad'Dib";
	char first[10] = {'\0'};
	char last[10] = {'\0'};
	if ((ret = regcomp(&reg, regex, REG_EXTENDED)) != 0) {
		regerror(ret, &reg, buf, sizeof(buf));
		goto stop;
	}
	if ((ret = regexec(&reg, s, nmatch, match, 0)) != 0) {
		regerror(ret, &reg, buf, sizeof(buf));
		goto stop;
	}
	for (int i = 1; i <= reg.re_nsub; i++) {
		if ((len = match[i].rm_eo - match[i].rm_so) > 0) {
			switch (i) {
			case 1:
				memcpy(first, s + match[i].rm_so, len);
			case 2:
				memcpy(last, s + match[i].rm_so, len);
			}
		}
	}
 stop:
	regfree(&reg);
	puts(buf);
	printf("first name: %s\nlast name: %s\n", first, last);
	return 0;
}

Une réaction?

Envoyez votre commentaire par mail.

Mode d'emploi de la liste de diffusion pour recevoir les réponses.