При работе с текстовыми данными одной из важных задач является сопоставление шаблону. Поиск шаблонов встречается постоянно для уточнения и выделения интересующих нас данных. На основании полученных данных можно принимать решение по фильтрации или редактированию материала.
Основным языком шаблонов в большинстве задач поиска выступает
язык регулярных выражений. Он позволяет описывать существенно более сложные в сравнении с предыдущим языком конструкции — регулярные (автоматные) языки по
классификации Хомского — , однако в этой классификации представляет наиболее узкое подмножество. В отличие от шаблонов
glob, синтаксис регулярных выражений допускает довольно сложные, а временами — трудно понимаемые конструкции.
Классическими утилитами, использующими регулярные выражения, являются утилита поиска по шаблону
grep и потоковый редактор
sed. Они поддерживают работу как с классическими регулярными выражениями, так и с
расширенными.
При разработке используются библиотеки, предназначенные для работы с регулярными выражениями. В стандартной библиотеке языка Си есть встроенная
обработка регулярных выражений. Существуют также диалекты регулярных выражений, выходящие за рамки автоматных грамматик (например, за счёт задания контекста появления шаблона) — таковы, например, регулярные выражения в языках
Perl и
Python.
7.1. Пример использования регулярных выражений в СИ
Соберём пакет, программы в котором будут выполнять роль утилиты
grep. Для сборки воспользуемся
Gear — утилитой, которая позволяет хранить авторские исходные тексты, спецификацию сборки и дополнительные файлы (например, исправления исходников или документацию, относящуюся к особенностям сборки для ALT) в едином каталоге под управлением системы контроля версий
Git:
@user
[user@VM ~]$ mkdir regex-pkg
[user@VM ~]$ cd regex-pkg/
[user@VM regex-pkg]$ git init
<...>
Initialized empty Git repository in /home/user/regex-pkg/.git/
[user@VM regex-pkg]$
В дальнейшем, поскольку вся сборка пакетов будет осуществляться с помощью Gear, данный блок с настройкой пустого git-репозитория будет опускаться:
@user:
regex-pkg/regex-no-bags.c
#include <stdio.h>
#include <stdlib.h>
#include <regex.h>
int main(int argc, char** argv) {
char *text;
size_t size = 0;
int len;
regex_t regex;
regcomp(®ex, argv[1], REG_EXTENDED);
for (text = NULL; (len = getline(&text, &size, stdin)) != -1; free(text), text = NULL) {
text[len - 1] = 0;
if (regexec(®ex, text, 0, NULL, 0) == 0)
puts(text);
}
regfree(®ex);
return 0;
}
Подробнее обсудим
ключевые функции для работы с регулярными выражениями:
regcomp на основе полученного шаблона строит структуру регулярного выражения для поиска подходящих шаблонов. Флаг REG_EXTENDED указывает на использование расширенных регулярных выражений;
regexec находит в указанной строке шаблон регулярного выражения по принципу «Самый левый, самый длинный»;
regfree освобождает структуру, выделенную в динамической памяти;
@user:
regex-pkg/regex-bags.c
#include <stdio.h>
#include <stdlib.h>
#include <regex.h>
#define MAXGR 10
int main(int argc, char** argv) {
char *text;
size_t size = 0;
int len;
regex_t regex;
regmatch_t bags[MAXGR];
regcomp(®ex, argv[1], REG_EXTENDED);
for (text = NULL; (len = getline(&text, &size, stdin)) != -1; free(text), text = NULL) {
text[len - 1] = '\0';
if (regexec(®ex, text, MAXGR, bags, 0) == 0) {
puts(text);
for(int i = 1; i <screen MAXGR && bags[i].rm_so >= 0; i++) {
int begin = bags[i].rm_so;
int end = bags[i].rm_eo;
printf("Bag %d: position %d - %d\t%.*s\n",
i, begin, end, end - begin, text + begin);
}
}
}
regfree(amp;regex);
return 0;
}
дополнительные параметры в regexec позволяют не только найти подходящую под шаблон подстроку, но и разбить её на «карманы» соответственно структуре регулярного выражения;
каждый карман описывается двумя параметрами: индексом начала и индексом конца кармана в исходной строке.
Makefile и spec-файл не отличаются никакими специальными командами или директивами:
@user:
regex-pkg/Makefile
GENS = regex-no-bags regex-bags
TRASH = *.o *~ o.*
CFLAGS = -Wall
CC = cc
all: regex-no-bags regex-bags
clean:
rm -f $(TRASH)
distclean: clean
rm -f $(GENS)
@user:
regex-pkg/regex-pkg.spec
Name: regex-pkg
Version: 1.0
Release: alt1
Summary: Test pkg with regex
License: GPL-3.0-or-later
Group: Development/Other
Source0: %name-%version.tar.gz
%description
This is a small testing package with Regular Expressions
%prep
%setup
%build
%make_build
%install
install -D regex-no-bags %buildroot%_bindir/regex-no-bags
install -D regex-bags %buildroot%_bindir/regex-bags
%files
%_bindir/*
%changelog
* Tue Jul 15 2025 UsamG1t <usamg1t@altlinux.org> 1.0-alt1
- Initial Build
Рассмотрим структуру gear-репозитория:
@user
[user@VM regex-pkg]$ tree
.
├── Makefile
├── regex-bags.c
├── regex-no-bags.c
└── regex-pkg.spec
1 directory, 4 files
[user@VM regex-pkg]$
Поскольку в gear-репозитории исходные тексты программ хранятся в нескомпонованном виде, необходимо указать правила компоновки и сжатия для будущей сборки пакета. Данные о компоновке описываются в файле
.gear/rules. Правила оформления
.gear/rules можно найти в
справочнике по gear:
@user:
.gear/rules
tar.gz: . name=@name@-@version@
Проведём сборку с помощью hasher. Для этого необходимо занести все изменения в git и запустить hasher:
@user
[user@VM regex-pkg]$ git add
[user@VM regex-pkg]$ gear-commit
[master (root-commit) 0080d40] 1.0-alt1
5 files changed, 99 insertions(+)
create mode 100644 .gear/rules
create mode 100644 Makefile
create mode 100644 regex-bags.c
create mode 100644 regex-no-bags.c
create mode 100644 regex-pkg.spec
[user@VM regex-pkg]$ gear-hsh
<...>
[user@VM regex-pkg]$ cd/
[user@VM ~]$ ls hasher/repo/x86_64/RPMS.hasher/ | grep regex
regex-pkg-1.0-alt1.x86_64.rpm
regex-pkg-debuginfo-1.0-alt1.x86_64.rpm
[user@VM ~]$
Соберём пустое окружение hasher и установим туда полученный пакет:
@user
[user@VM ~]$ hsh --init
<...>
[user@VM ~]$ cp hasher/repo/x86_64/RPMS.hasher/regex-pkg-1.0-alt1.x86_64.rpm hasher/ch
root/.in/
[user@VM ~]$ hsh-shell --rooter
@rooter
[root@localhost .in]# rpm -i regex-pkg-1.0-alt1.x86_64.rpm
<13>Jul 15 15:03:44 rpm: regex-pkg-1.0-alt1 1752591330 installed
[root@localhost .in]#
[root@localhost .in]# which regex-bags
/usr/bin/regex-bags
[root@localhost .in]# which regex-no-bags
/usr/bin/regex-no-bags
[root@localhost .in]# cal | regex-no-bags "(2)(.*)(3)"
1 2 3 4 5
20 21 22 23 24 25 26
27 28 29 30 31
[root@localhost .in]# cal | regex-bags "(2)(.*)(3)"
1 2 3 4 5
Bag 1: position 10 - 11 2
Bag 2: position 11 - 13
Bag 3: position 13 - 14 3
20 21 22 23 24 25 26
Bag 1: position 0 - 1 2
Bag 2: position 1 - 10 0 21 22 2
Bag 3: position 10 - 11 3
27 28 29 30 31
Bag 1: position 0 - 1 2
Bag 2: position 1 - 12 7 28 29 30
Bag 3: position 12 - 13 3
[root@localhost .in]#