Книга: Linux программирование в примерах
12.8. Регулярные выражения
12.8. Регулярные выражения
Регулярные выражения являются способом описания текстовых шаблонов для сопоставления. Если вы вообще сколько-нибудь использовали GNU/Linux или Unix, вы без сомнения знакомы с регулярными выражениями: они являются фундаментальной частью инструментария программиста Unix. Они неотъемлемы от таких повседневных программ, как grep
, egrep
, sed
, awk
, Perl, а также редакторы ed
, vi
, vim
и Emacs. Если вы вообще не знакомы с регулярными выражениями, мы рекомендуем ознакомиться с некоторыми из книг или URL, указанных в разделе 12.9 «Рекомендуемая литература».
POSIX определяет два вида регулярных выражений: базовый и расширенный. Программы типа grep
, sed
и строчный редактор ed
используют базовые регулярные выражения. Программы типа egrep
и awk
используют расширенные регулярные выражения. Следующие функции дают вам возможность использовать в своих программах любой вид.
#include <sys/types.h> /* POSIX */
#include <regex.h>
int regcomp(regex_t *preg, const char *regex, int cflags);
int regexec(const regex_t *preg, const char *string, size_t nmatch,
regmatch_t pmatch[], int eflags);
size_t regerror(int errcode, const regex_t *preg,
char *errbuf, size_t errbuf_size);
void regfree(regex_t *preg);
Чтобы сопоставить регулярное выражение, нужно сначала откомпилировать строчную версию регулярного выражения. Компиляция преобразует регулярное выражение во внутренний формат. Затем откомпилированная форма исполняется для строки для проверки, совпадает ли она с первоначальным регулярным выражением. Функции следующие.
int regcomp(regex_t *preg, const char *regex, int cflags)
Компилирует регулярное выражение regex
во внутреннее представление, сохраняя его в структуре regex_t
, на которую указывает preg
. cflags
контролирует процесс компиляции; ее значение равно 0 или побитовому ИЛИ одного или более флагов из табл. 12.7
int regexec(const regex_t *preg, const char *string, size_t nmatch,
regmatch_t pmatch[], int eflags)
Выполняет откомпилированное регулярное выражение в *preg
в строке string eflags
контролирует способ выполнения; ее значение равно 0 или побитовому ИЛИ одного или более флагов из табл. 12.8. Вскоре мы обсудим другие аргументы.
size_t regerror(int errcode, const regex_t *preg,
char *errbuf, size_t errbuf_size)
Преобразует ошибку, возвращенную regcomp()
или regexec()
, в удобочитаемую строку.
void regfree(regex_t *preg)
Освобождает динамическую память, используемую откомпилированным регулярным выражением в *preg
.
Заголовочный файл <regex.h>
определяет ряд флагов. Некоторые используются с regcomp()
; другие используются с regexec()
. Однако, все они начинаются с префикса 'REG_
'. В табл. 12.7 перечислены флаги для компиляции регулярных выражений с помощью regcomp()
.
Таблица 12.7. Флаги для regcomp()
Константа | Значение |
---|---|
REG_EXTENDED |
Использовать расширенные регулярные выражения. По умолчанию используются базовые регулярные выражения |
REG_ICASE |
Сопоставление regexec() игнорирует регистр символов |
REG_NEWLINE |
Операторы, заменяющие любой символ, не включают символ конца строки |
REG_NOSUB |
Информация о начале и конце вложенною шаблона не требуется (см текст) |
Флаги для сопоставления регулярных выражений с помощью regexec()
приведены в табл. 12.8.
Таблица 12.8. Флаги дли regexec()
Константа | Значение |
---|---|
REG_NOTBOL |
Оператор ^ (начало строки) не сопоставляется |
REG_NOTEOL |
Оператор $ (конец строки) не сопоставляется |
Флаги REG_NEWLINE
, REG_NOTBOL
и REG_NOTEOL
взаимодействуют друг с другом. Это немного запутано, поэтому мы будем продвигаться небольшими шажками.
• Когда в cflags
не включен REG_NEWLINE
, символ конца строки действует в качестве обычного символа. С ним может быть сопоставлен метасимвол '.
' (любой символ), а также дополненные списки символов ('[^...]
'). При этом $
не сопоставляется немедленно с началом вставленного символа новой строки, а ^
не сопоставляется немедленно с его концом.
• Когда в eflags
установлен REG_NOTBOL
, оператор ^
не соответствует началу строки. Это полезно, когда параметр string
является адресом символа в середине сопоставляемого текста.
• Сходным образом, когда в eflags
установлен REG_NOTEOL
, оператор $
не соответствует концу строки.
• Когда в cflags
включен REG_NEWLINE
, то:
• Символ конца строки не соответствует '.
' или дополненному списку символов.
• Оператор ^
всегда соответствует положению непосредственно за вставленным символом конца строки независимо от установки REG_BOL
.
• Оператор $
всегда соответствует положению непосредственно перед вставленным символом конца строки независимо от установки REG_EOL
.
Когда вы осуществляете построчный ввод/вывод, как в случае с grep
, можно не включать REG_NEWLINE
в cflags
. Если в буфере несколько строк, и каждую из них нужно рассматривать как отдельную, с сопоставлением ^
и $
, тогда следует включить REG_NEWLINE
.
Структура regex_t
по большей части непрозрачна. Код уровня пользователя может исследовать лишь один член этой структуры; остальное предназначено для внутреннего использования процедурами регулярных выражений:
typedef struct {
/* ...здесь внутренний материал... */
size_t re_nsub;
/* ...здесь внутренний материал... */
} regex_t;
В структуре regmatch_t
есть по крайней мере два члена для использования кодом уровня пользователя:
typedef struct {
/* ...здесь возможный внутренний материал... */
regoff_t rm_so; /* Смещение начала вложенной строки в байтах */
regoff_t rm_eo; /* Смещение первого байта после вложенной строки */
/* ...здесь возможный внутренний материал... */
} regmatch_t;
Как поле re_nsub
, так и структура regmatch_t
предназначены для сопоставления вложенных выражений. Рассмотрим такое регулярное выражение:
[:пробел:]]+([[:цифра:]]+)[[:пробел:]]+([[:буква:]])+
Каждое из двух вложенных выражений в скобках могут соответствовать одному или более символам. Более того, текст, соответствующий каждому вложенному выражению, может начинаться и заканчиваться в произвольных участках строки.
regcomp()
устанавливает в поле re_nsub
число вложенных выражений в скобках внутри регулярного выражения, regexec()
заполняет массив pmatch
структур regmatch_t
смещениями начальных и конечных байтов текста, соответствующих этим вложенным выражениям. Вместе эти данные позволяют заменять текст — удалять его или заменять другим текстом, точно так же, как в текстовом редакторе
pmatch[0]
описывает часть строки, соответствующую всему регулярному выражению. Участок от pmatch[1]
до pmatch[preg->re_nsub]
описывает ту часть, которая соответствует каждому вложенному выражению в скобках. (Таким образом, вложенные выражения нумеруются начиная с 1.) Элементы rm_so
и rm_eo
не используемых элементов массива pmatch
установлены в -1.
regexec()
заполняет не более nmatch-1
элементов pmatch
; поэтому следует убедиться, что имеется по крайней мере на 1 элемент больше, чем в preg->re_nsub
.
Наконец, флаг REG_NOSUB
для regcomp()
означает, что начальная и завершающая информация не нужна. Этот флаг следует использовать, когда эти сведения не нужны; это потенциально может довольно значительно повысить производительность regexec()
.
Другими словами, если все, что вам нужно знать, это «соответствует ли?», включите REG_NOSUB
. Однако, если нужно также знать, «где находится соответствующий текст?», этот флаг следует опустить.
В заключение, как regcomp()
, так и regexec()
возвращают 0, если они успешны, или определенный код ошибки, если нет. Коды ошибок перечислены в табл. 12.9.
Таблица 12.9. Коды ошибок regcomp()
и regexec()
Константа | Значение |
---|---|
REG_BADBR |
Содержимое '{...} ' недействительно. |
REG_BADPAT |
Регулярное выражение недействительно |
REG_BADRPT |
Символу ? , + или * не предшествует действительное регулярное выражение. |
REG_EBRACE |
Фигурные скобки ('{...} ') не сбалансированы |
REG_EBRACK |
Квадратные скобки ('[...] ') не сбалансированы |
REG_ECOLLATE |
В шаблоне использован недействительный элемент сортировки |
REG_ECTYPE |
В шаблоне использован недействительный класс символов |
REG_EESCAPE |
В шаблоне есть завершающий символ |
REG_EPAREN |
Группирующие скобки ('(...) ' или '(...) ') не сбалансированы |
REG_ERANGE |
Конечная точка в диапазоне не действительна |
REG_ESPACE |
Функции не хватило памяти |
REG_ESUBREG |
Цифра в 'цифра ' недействительна |
REG_NOMATCH |
Строка не соответствует шаблону |
Для демонстрации регулярных выражений ch12-grep.c
предусматривает базовую реализацию стандартной программы grep
, которая отыскивает соответствие шаблону. Наша версия использует по умолчанию базовые регулярные выражения. Для использования вместо этого расширенных регулярных выражений она принимает опцию -E
, а для игнорирования регистра символов опцию -i
. Как и настоящая grep
, если в командной строке не указаны файлы, наша grep
читает со стандартного ввода, а для обозначения стандартного ввода, как и в настоящей grep
, может быть использовано имя файла '-
'. (Эта методика полезна для поиска в стандартном вводе наряду с другими файлами.) Вот программа:
1 /* ch12-grep.c - Простая версия grep, использующая функции POSIX */
2
3 #define _GNU_SOURCE 1 /* для getline)) */
4 #include <stdio.h>
5 #include <errno.h>
6 #include <regex.h>
7 #include <unistd.h>
8 #include <sys/types.h>
9
10 char *myname; /* для сообщений об ошибках */
11 int ignore_case = 0; /* опция -i: игнорировать регистр */
12 int extended = 0; /* опция -E: использовать расширенные регулярные выражения */
13 int errors = 0; /* число ошибок */
14
15 regex_t pattern; /* шаблон для поиска */
16
17 void compile_pattern(const char *pat);
18 void process(const char *name, FILE *fp);
19 void usage(void);
Строки 10–15 объявляют глобальные переменные программы. Первый набор (строки 10–13) для опций и сообщений об ошибках. Строка 15 объявляет pattern
, в которой хранится откомпилированный шаблон. Строки 17–19 объявляют другие функции программы.
21 /* main --- обработка опций, открывание файлов */
22
23 int main(int argc, char **argv)
24 {
25 int с;
26 int i;
27 FILE *fp;
28
29 myname = argv[0];
30 while ((c = getopt(argc, argv, ":iE")) != -1) {
31 switch (c) {
32 case 'i':
33 ignore_case = 1;
34 break;
35 case 'E':
36 extended = 1;
37 break;
38 case '?':
39 usage();
40 break;
41 }
42 }
43
44 if (optind == argc) /* проверка исправности */
45 usage();
46
47 compile_pattern(argv[optind]); /* компилировать шаблон */
48 if (errors) /* ошибка компиляции */
49 return 1;
50 else
51 optind++;
В строке 29 устанавливается значение myname
, а строки 30–45 анализируют опции. Строки 47–51 компилируют регулярное выражение, помещая результаты в pattern
, compilе_раttern()
увеличивает значение errors
, если была проблема. (Соединение функций посредством глобальной переменной, как здесь, обычно считается плохой манерой. Для небольших программ, подобным этой, это сойдет, но для более крупных программ такое сопряжение может стать проблемой.) Если не было ошибок, строка 51 увеличивает значение optind
так, что оставшиеся аргументы представляют файлы для обработки.
53 if (optind == argc) /* файлов нет, по умолчанию stdin */
54 process("standard input", stdin);
55 else {
56 /* цикл с файлами */
57 for (i = optind; i < argc; i++) {
58 if (strcmp(argv[i], "-") == 0)
59 process("standard input", stdin);
60 else if ((fp = fopen(argv[i], "r")) != NULL) {
61 process(argv[i], fp);
62 fclose(fp);
63 } else {
64 fprintf(stderr, "%s: %s: could not open: %sn",
65 argv[0], argv[i], strerror(errno));
66 errors++;
67 }
68 }
69 }
70
71 regfree(&pattern);
72 return errors != 0;
73 }
Строки 53–69 обрабатывают файлы, отыскивая соответствующие шаблону строки. Строки 53–54 обрабатывают случай, когда файлы не указаны: программа читает со стандартного ввода. В противном случае, строки 57–68 обрабатывают в цикле файлы. Строка 58 обрабатывает особый случай '-
', обозначающий стандартный ввод, строки 60–62 обрабатывают обычные файлы, а строки 63–67 обрабатывают ошибки.
75 /* compile_pattern --- компиляция шаблона */
76
77 void compile_pattern(const char *pat)
78 {
79 int flags = REG_NOSUB; /* информация о месте совпадения не требуется */
80 int ret;
81 #define MSGBUFSIZE 512 /* произвольно */
82 char error[MSGBUFSIZE];
83
84 if (ignore_case)
85 flags |= REG_ICASE;
86 if (extended)
87 flags |= REG_EXTENDED;
88
89 ret = regcomp(&pattern, pat, flags);
90 if (ret != 0) {
91 (void)regerror(ret, &pattern, error, sizeof error);
92 fprintf(stderr, "%s: pattern '%s': %sn", myname, pat, error);
93 errors++;
94 }
95 }
Строки 75–95 определяют функцию compile_pattern()
. Она сначала устанавливает REG_NOSUB
в flags
, поскольку нам нужно знать лишь «подходит ли строка?», а не «где в строке располагается подходящий текст?»
Строки 84-85 добавляют дополнительные флаги в соответствии с опциями командной строки. Строка 89 компилирует шаблон, а строки 90–94 сообщают о возникших ошибках
97 /* process --- читает строки текста и сопоставляет их с шаблоном */
98
99 void process(const char *name, FILE *fp)
100 {
101 char *buf = NULL;
102 size_t size = 0;
103 char error[MSGBUFSIZE];
104 int ret;
105
106 while (getline(&buf, &size, fp) != -1) {
107 ret = regexec(&pattern, buf, 0, NULL, 0);
108 if (ret != 0) {
109 if (ret != REG_NOMATCH) {
110 (void)regerror(ret, &pattern, error, sizeof error);
111 fprintf(stderr, "%s: file %s: %sn", myname, name, error);
112 free(buf);
113 errors++;
114 return;
115 }
116 } else
117 printf("%s: %s", name, buf); /* вывести подходящие строки */
118 }
119 free(buf);
120 }
Строки 97–120 определяют функцию process()
, которая читает файл и выполняет сопоставление с регулярным выражением. Внешний цикл (строки 106–119) читает строки ввода. Для избежания проблем с длиной строки мы используем getline()
(см. раздел 3.2.1.9 «Только GLIBC: чтение целых строк: getline()
и getdelim()
»). Строка 107 вызывает regexec()
. Ненулевое возвращаемое значение означает либо неудачное сопоставление, либо какую-нибудь другую ошибку. Строки 109–115 соответственно проверяют REG_NOMATCН
и выводят ошибку лишь тогда, когда возникла какая-нибудь другая проблема — неудачное сопоставление не является ошибкой
Если возвращаемое значение равно 0, строка совпала с шаблоном и соответственно строка 117 выводит имя файла и совпавшую строку.
122 /* usage --- вывод сообщения об использовании и выход */
123
124 void usage(void)
125 {
126 fprintf(stderr, "usage: %s [-i] [-E] pattern [ files ... ]n", myname);
127 exit(1);
128 }
Функция usage()
выводит сообщение об использовании и завершает программу. Она вызывается, когда предоставлены недействительные аргументы или не предоставлен шаблон (строки 38–40 и 44–45).
Вот и все! Скромная, но тем не менее полезная версия grep
в 130 строк кода.
- 12.1. Операторы проверки: assert()
- 12.2. Низкоуровневая память: функции memXXX()
- 12.3. Временные файлы
- 12.4. Совершение самоубийства: abort()
- 12.5. Нелокальные переходы
- 12.6. Псевдослучайные числа
- 12.7. Расширения метасимволов
- 12.8. Регулярные выражения
- 12.9. Рекомендуемая литература
- 12.10. Резюме
- Упражнения
- Выражения в EXCEPTION
- 6. Лекция: Обработка текстов. Регулярные выражения. Unicode.
- 4.2.2. Возвращаясь к строкам и регулярным выражениям
- 2.11 Условные выражения
- ГЛАВА 21. Выражения и предикаты.
- Лекция 8. Регулярные выражения
- Условные выражения
- Глава 18. Регулярные выражения
- Глава 10. Конечные автоматы и регулярные выражения.
- Регулярные выражения
- Булевы выражения
- 9. Регулярные выражения