Product SiteDocumentation Site

Глава 7. Сопоставление шаблону

7.1. Пример использования регулярных выражений в СИ
При работе с текстовыми данными одной из важных задач является сопоставление шаблону. Поиск шаблонов встречается постоянно для уточнения и выделения интересующих нас данных. На основании полученных данных можно принимать решение по фильтрации или редактированию материала.
Одним из примитивных языков шаблонов выступает представленный в Shell вариант. Также этот язык шаблонов оформлен в качестве отдельной утилиты glob и библиотечного вызова glob(). Он используется преимущественно для проверок соответствия пути файлов.
Основным языком шаблонов в большинстве задач поиска выступает язык регулярных выражений. Он позволяет описывать существенно более сложные в сравнении с предыдущим языком конструкции — регулярные (автоматные) языки по классификации Хомского  — , однако в этой классификации представляет наиболее узкое подмножество. В отличие от шаблонов 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(&regex, argv[1], REG_EXTENDED);

       for (text = NULL; (len = getline(&text, &size, stdin)) != -1; free(text), text = NULL) {
	       text[len - 1] = 0;
	       if (regexec(&regex, text, 0, NULL, 0) == 0)
		       puts(text);
       }

       regfree(&regex);
       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(&regex, argv[1], REG_EXTENDED);
    
           for (text = NULL; (len = getline(&text, &size, stdin)) != -1; free(text), text = NULL) {
    	       text[len - 1] = '\0';
    	       if (regexec(&regex, 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]#