Программирование в стандарте POSIX

       

Использование регулярных выражений


Мы приступаем к описанию наиболее употребительных служебных программ и функций, использующих механизм регулярных выражений. Безусловно, на первое место следует поставить утилиту grep:

grep [-E | -F] [-c | -l | -q] [-insvx] -e список_шаблонов ... [-f файл_шаблонов] ... [файл ...]

grep [-E | -F] [-c | -l | -q] [-insvx] [-e список_шаблонов ...] -f файл_шаблонов ... [файл ...]

grep [-E | -F] [-c | -l | -q] [-insvx] список_шаблонов [файл ...]

Она служит для выборки строк исходных файлов, удовлетворяющих хотя бы одному шаблону из заданного списка.

Три приведенные выше варианта вызова служебной программы grep отличаются способом задания списка шаблонов. В первом случае он является аргументом опции -e, во втором извлекается из файла шаблонов - аргумента опции -f, в третьем указывается как самостоятельный аргумент командной строки, но отдельные элементы этого списка всегда разделяются символами перевода строки. Опция -e полезна в ситуациях, когда нужно задать шаблон, начинающийся со знака минус.

По умолчанию шаблоны трактуются как базовые регулярные выражения. Опция -E предписывает переход к расширенным РВ, а опция -F означает, что в качестве шаблонов выступают цепочки символов (и, следовательно, процесс сопоставления существенно упрощается).

Опции -c, -l, -q и -n влияют на выдачу результатов. По умолчанию на стандартный вывод выдаются строки исходных файлов, в которых присутствуют успешно сопоставленные   цепочки символов. Опция -c предписывает выдавать только общее число подобных строк, -l - только имена файлов, где имеются успешно сопоставленные строки, -q - только код завершения (0 - есть успешно сопоставленные строки), -n требует ставить ее номер в исходном файле перед каждой выводимой строкой (нумерация строк начинается с 1).

Опции -i, -v и -x воздействуют на процесс сопоставления: -i предписывает не различать при сопоставлении большие и малые буквы, -v - выбирать строки, не удовлетворяющие ни одному из заданных шаблонов, -x - рассматривать только строки, все символы которых участвуют в успешном сопоставлении с одним из шаблонов.


Опция - s подавляет выдачу диагностических сообщений о том, что исходный файл не существует или не доступен на чтение.

Отметим, что опции -F и -q, каждая по-своему, ускоряют работу служебной программы grep: -F упрощает сопоставление, -q позволяет завершить действие после первого успешного сопоставления (и не обрабатывать оставшиеся строки и/или файлы). Сочетание опций -q и -s позволяет также более свободно задавать исходные файлы, не заботясь об их существовании и доступности.

Рассмотрим примеры использования утилиты grep. Для выборки пустых строк из файла стандартного ввода пригодны два шаблона:

grep ^$ grep -v .

Если нужно выбрать строки, имеющие вид abc или def, можно воспользоваться одной из трех команд:

grep -E '^abc$|^def$' grep -F -x 'abc def'

И наконец, пусть в файлах с исходными текстами Фортран-программ требуется найти все строки, содержащие вызовы подпрограмм и не являющиеся комментариями. Для такой выборки "в первом приближении" (учитывая нерегулярний синтаксис Фортрана) подойдет следующая команда:

grep -i '^[^C].* CALL ' *.for

В командных файлах для обработки текстов часто используется потоковый редактор   sed:



sed [-n] сценарий [файл ...] sed [-n] [-e сценарий] ... [-f файл_сценария] ... [файл ...]

Редактор sed читает указанные текстовые файлы (по умолчанию - стандартный ввод), выполняет редактирование в соответствии с командами сценария и записывает результат на стандартный вывод. Смысл опций -e и -f аналогичен утилите grep. Опция -n подавляет подразумеваемый вывод и предписывает выдавать только явно отобранные строки.

Сценарий для sed состоит из редактирующих команд (каждая на отдельной строке), имеющих следующий формат:

[адрес [, адрес]] функция [аргумент ...]

Функция имеет здесь однобуквенное обозначение.

В нормальном режиме sed циклически выполняет следующие действия:

  • Добавляет входную строку в буфер. Обычно буфер пуст, если только предыдущий цикл не завершился командой D.
  • Применяет к буферу последовательно все команды сценария, адреса в которых позволяют их применить.
  • Если не указана опция -n, копирует буфер на стандартный вывод, добавив в конце перевод строки.Очищает буфер.


Некоторые команды используют хранилище, чтобы запомнить весь буфер или его часть для последующего применения.

Адрес в редактирующей команде sed - это либо десятичное число, означающее номер входной строки в совокупности входных файлов, либо символ $, который обозначает последнюю входную строку, либо контекстный адрес, имеющий вид /базовое_регулярное_выражение/. Контекстный адрес задает первую (начиная с текущей) из строк, успешно сопоставленных с БРВ при движении вперед.

Командная строка без адреса применима к любому буферу, командная строка с одним адресом - к буферу с соответствующим адресом, командная строка с двумя адресами - к буферам с адресами в диапазоне от первого до второго включительно; затем процесс повторяется, начиная с первой строки вслед за выбранным диапазоном.



Очищает буфер.

Некоторые команды используют хранилище, чтобы запомнить весь буфер или его часть для последующего применения.

Адрес в редактирующей команде sed - это либо десятичное число, означающее номер входной строки в совокупности входных файлов, либо символ $, который обозначает последнюю входную строку, либо контекстный адрес, имеющий вид /базовое_регулярное_выражение/. Контекстный адрес задает первую (начиная с текущей) из строк, успешно сопоставленных с БРВ при движении вперед.

Командная строка без адреса применима к любому буферу, командная строка с одним адресом - к буферу с соответствующим адресом, командная строка с двумя адресами - к буферам с адресами в диапазоне от первого до второго включительно; затем процесс повторяется, начиная с первой строки вслед за выбранным диапазоном.

Перечислим команды редактора sed. В скобках указывается максимальное число допустимых адресов для каждой функции.

Аргумент текст состоит из одной или более строк. Все строки, кроме последней, заканчиваются на \, чтобы экранировать символ перевода строки.

(2){ функция функция ... }

Выполнить заданную последовательность функций.

(1)a\ текст

Добавить. Вывести текст перед чтением следующей входной строки.

(2)b [метка]

Перейти к команде :, содержащей метку. Если метка пуста, перейти на конец сценария.

(2)c\ текст

Заменить. Удалить содержимое буфера. При 0 или 1 адресе или в конце двухадресного диапазона вывести текст. Начать новый цикл.

(2)d

Удалить содержимое буфера. Начать новый цикл.

(2)D

Удалить начало буфера до первого перевода строки. Начать новый цикл.

(2)g

Заменить содержимое буфера содержимым хранилища.

(2)G

Добавить к содержимому буфера содержимое хранилища.

(2)h

Заменить содержимое хранилища содержимым буфера.

(2)H

Добавить к содержимому хранилища содержимое буфера.

(1)i\ текст

Вставить. Вывести текст.

(2)l

Вывести буфер, заменяя непечатные символы на пары символов ASCII и разбивая длинные строки.

(2)n

Скопировать буфер на стандартный вывод, если подразумеваемый вывод не подавлен.


Заменить содержимое буфера на следующую входную строку. Если таковой не оказалось, завершить выполнение сценария.

(2)N

Добавить к буферу следующую входную строку, вставив перед ней символ перевода строки. (Текущий номер строки изменяется.) Если входных строк больше нет, завершить выполнение сценария.

(2)p

Скопировать буфер на стандартный вывод.

(2)P

Скопировать начальный сегмент буфера (до первого перевода строки) на стандартный вывод.

(1)q

Выйти. Перейти на конец сценария. Нового цикла не начинать.

(2)r ч_файл

Прочитать содержимое ч_файла. Поместить его на стандартный вывод перед чтением следующей входной строки.

(2)s/БРВ/замена/флаги

Подставить замену вместо фрагментов буфера, отождествленных с БРВ. Флаги могут быть опущены или иметь следующие значения:

  • число n - заменить n-е вхождение БРВ;


  • g - заменить все вхождения БРВ, а не только первое;


  • p - если замена произошла, вывести содержимое буфера;


  • w з_файл - если замена произошла, добавить содержимое буфера к з_файлу.


Вместо символа & в замене подставляется цепочка, отождествленная с БРВ.

(2)t [метка]

Проверить. Перейти к команде :, содержащей метку, если со времени последнего чтения входной строки или последнего выполнения команды t в буфере производились подстановки. Если метка пуста, перейти на конец сценария.

(2)w з_файл

Записать. Добавить содержимое буфера к з_файлу.

(2)x

Обменять содержимое буфера и хранилища.

(2)y/цепочка1/цепочка2/

Заменить все символы буфера, содержащиеся в цепочке1, на соответствующие символы цепочки2. Длины цепочек должны совпадать.

(2)! функция

Отрицание. Применить функцию (или группу, если функция начинается с {) только к строкам, не соответствующим адресам.

(0): метка

Не делает ничего. Содержит лишь метку, на которую может быть осуществлен переход командами t или b.

(1)=

Вывести в качестве отдельной строки номер текущей строки.

(0)

Пустая команда.

(0)#

Управляющий комментарий. Если сценарий начинается с символов #n, подразумеваемый вывод подавляется (что эквивалентно опции -n в командной строке).


В остальных случаях игнорировать # и остаток строки.

Последовательность символов \n успешно сопоставляется с переводом строки. Явный символ перевода строки не должен использоваться в БРВ контекстных адресов и функции замены.

Приведем примеры использования потокового редактора   sed. В процессе загрузки ОС Linux выполняются командные строки, аналогичные показанным в пример 6.23.

map=`basename $map | sed -e s/^auto_home/auto.home/ -e s/^auto_mnt/auto.mnt/` cat /etc/auto.master | grep -v '^+' | sed -e '/^#/d' -e '/^$/d'

Листинг 6.23. Пример использования редактора sed.

Первая из них заменяет подчеркивание на точку в именах файлов, обслуживающих автомонтирование файловых систем, вторая отсеивает строки файла auto.master, начинающиеся с символа + (это делает grep -v), комментарии (строки, начинающиеся символом #) и пустые строки.

Следующий вызов sed (см. пример 6.24) сжимает несколько идущих подряд пустых строк в одну.

sed -n ' p /^$/ { # Текущая строка - пустая. # Добавляем следующие строки к буферу, # пока он остается пустым. # Тем самым игнорируются "лишние" пустые # строки. :Empty n /^$/ b Empty # Добавленная строка оказалась непустой. # Выведем ее. p } '

Листинг 6.24. Сжатие пустых строк средствами редактора sed.

Любопытно сопоставить приведенный нами сценарий с примером, включенным в текст стандарта POSIX-2001 (см. пример 6.25). Наш вариант явно проще и короче.

sed -n ' # Выведем непустые строки /./ { p d } # Выведем одну пустую строку, затем # проанализируем следующие. /^$/ p # Прочитаем следующую строку, отбросим # оставшийся перевод строки (пустую строку) # и вернемся к проверке пустой строки. :Empty /^$/ { N s /.// b Empty } # Выведем непустую строку, затем вернемся к # поиску первой пустой. p '

Листинг 6.25. «Стандартный» вариант сжатия пустых строк средствами редактора sed.

Еще одно популярное средство обработки текстовых файлов - служебная программа awk:

awk [-F РРВ] [-v присваивание] ... программа [аргумент ...] awk [-F РРВ] -f программный_файл ... [-v присваивание] ... [аргумент ...]



Утилита awk выполняет программы, написанные на одноименном языке программирования, специально предназначенном для обработки текстов. Программа на языке awk представляет собой последовательность шаблонов и соответствующих действий, которые выполняются при чтении входных данных, успешно сопоставляющихся с шаблоном. Оператор шаблон-действие имеет вид

шаблон { действие }

Ввод для awk делится на записи, разделяемые специальным символом. По умолчанию это перевод строки; в таком случае awk обрабатывает ввод построчно. Разделитель записей можно изменить, переопределив переменную   RS. Каждая запись делится на поля, ограниченные разделителями полей (по умолчанию - пробелами или табуляциями). Любой из них можно изменить, переопределив переменную   FS или указав опцию -F с аргументом - расширенным регулярным выражением (РРВ). Поля исходных строк доступны по именам $1, $2,...; $0 - вся входная строка.

Каждая исходная строка сопоставляется с каждым из шаблонов; в случае успеха выполняются указанные действия. После сопоставления со всеми шаблонами вводится следующая строка и процесс сопоставления повторяется. Может быть опущен либо шаблон, либо действие, но не оба вместе. Если для данного шаблона не указаны действия, то строка просто копируется на стандартный вывод. Если для действия не определен шаблон, то оно будет выполняться для каждой входной строки. Строки, которые не удалось сопоставить ни одному шаблону, игнорируются.

Действие есть последовательность операторов. Поскольку шаблоны и действия могут быть опущены, то, чтобы различать их в программе, последние надо брать в фигурные скобки. Оператор - это одна из конструкций:

  1. if ( условие ) оператор [ else оператор ];
  2. while ( условие ) оператор;
  3. for ( выражение; условие; выражение ) оператор;
  4. break;
  5. continue;
  6. { [ оператор ] ... };
  7. переменная = выражение # оператор присваивания;
  8. print [ список_выражений ] [> выражение ];
  9. printf формат [, список_выражений ] [> выражение ];
  10. next # пропустить оставшиеся шаблоны и перейти к следующей строке;
  11. exit # пропустить оставшиеся строки.




Операторы завершаются точкой с запятой, переводом строки или правой скобкой. Пустой список_выражений означает всю строку. Выражения строятся из цепочек символов и чисел с помощью операций +, -, *, /, %, ^ (возведение в степень) и конкатенации (обозначается пробелом). В них также можно использовать операции из языка C: ++, --, +=, -=, *=, /=, %=, ^=, ? : (условное выражение). Переменные инициализируются пустыми цепочками, могут быть скалярами, элементами массива (обозначается x[i]) или полями. Индексами массива служат любые (не обязательно числовые) цепочки символов, что позволяет реализовать разновидность ассоциативной памяти. Цепочки символов заключаются в двойные кавычки (").

Оператор   print выдает свои аргументы на стандартный вывод (или в файл, если присутствует часть >выражение), разделяя их текущим разделителем полей и завершая каждую запись выходным разделителем записей. Оператор   printf делает то же, но под управлением формата.

Язык awk содержит большое число встроенных функций. Кратко опишем их.

Математические функции atan2 (y, x), cos (x), sin (x), exp (x), log (x), sqrt (x) не нуждаются в пояснениях. Функция int (x) отбрасывает дробную часть своего аргумента, rand () возвращает псевдослучайное число в диапазоне от

В число функций, оперирующих цепочками символов, входят gsub (РРВ, замена[, цепочка]) и sub (РРВ, замена[, цепочка]) - соответственно, глобальная и однократная замена вхождений РРВ в $0 или цепочку, по аналогии с командой s редактора sed и ее флагом g; index (цепочка, подцепочка) - поиск подцепочки в цепочке; length [([цепочка])] - вычисление длины цепочки-аргумента или $); match> (цепочка, РРВ) - поиск вхождения РРВ в цепочку с установкой значений переменных   RSTART и RLENGTH (см. далее); split (цепочка, массив[, РРВ-разделитель]) - расщепление цепочки по полям в элементы массива); sprintf (формат, выражение, выражение, ...) - формирование цепочки символов средствами форматного вывода; substr (цепочка, m[, n]) - выделение n-символьной подцепочки, начинающейся с позиции m; tolower (цепочка) - приведение к строчным буквам; toupper (цепочка) - приведение к прописным буквам.



В языке awk имеются также группа функций ввода/вывода и функции общего назначения. Функция close (выражение) закрывает файл или канал, поименованный заданным выражением, getline [переменная] обеспечивает чтение записи из текущего входного файла (возможно использование конвейера вида выражение   | getline [переменная] и перенаправление ввода getline [переменная] < выражение), system (выражение) - выполнение команды, заданной выражением.)

Язык awk допускает определение пользовательских функций, для чего служит конструкция

function имя_функции ([аргумент, ...]) { операторы }

Шаблон в языке awk - это произвольная логическая комбинация, составленная с помощью операций !, ||, && и скобок из расширенных регулярных выражений и выражений сравнения. РРВ обрамляются символами /. Отдельное РРВ в шаблоне сопоставляется со всей строкой. РРВ допускаются и в выражениях сравнения. Шаблон может состоять из двух шаблонов, разделенных запятой; указанные действия выполняются для всех строк между строкой, удовлетворяющей первому шаблону, и строкой, удовлетворяющей второму.

Выражение сравнения - одна из следующих конструкций:

выражение опер_сопост РРВ выражение опер_сравн выражение

Здесь опер_сравн - любая из шести операций сравнения языка C, опер_сопост - это ~ (успешно сопоставляется) или !~ (не сопоставляется).

Условие - арифметическое выражение, выражение сравнения или их логическая комбинация.

Для выполнения каких-либо действий перед чтением первой или после чтения последней исходной строки определены специальные шаблоны BEGIN и END. Шаблон BEGIN следует указывать первым, END - последним.

Присваивания, заданные в командной строке с помощью опции -v, выполняются до начала интерпретации awk-программы (в частности, до действий, ассоциированных с шаблоном BEGIN). Например, для использования символа c в качестве разделителя полей можно указать в командной строке -v 'FS = c'.

В командной строке можно указать также аргументы двух типов - имена файлов с исходными данными и присваивания.


Последние выполняются непосредственно перед чтением указанного следующим исходного файла. В частности, присваивания, заданные перед первым аргументом-файлом выполняются после действий, ассоциированных с шаблоном BEGIN, а те, что расположены в конце командной строки, - перед действиями, ассоциированными с шаблоном END. Если в командной строке нет аргументов-файлов, присваивания выполняются перед обработкой стандартного ввода.

Перечислим специальные переменные awk.

ARGC

Число элементов в массиве ARGV.

ARGV

Массив аргументов командной строки awk, исключая опции и программы.

CONVFMT

Формат для преобразования чисел в цепочки символов (кроме операторов вывода, где используется переменная   OFMT, см. далее). По умолчанию - %.6g.

ENVIRON

Массив, представляющий окружение. Индексами служат цепочки символов, совпадающих с именами переменных окружения.

FILENAME

Имя файла, из которого в данный момент производится ввод.

FNR

Порядковый номер текущей записи в текущем исходном файле.

FS

РРВ - разделитель полей во входных данных, по умолчанию - пробел.

NF

Количество полей в текущей записи.

NR

Порядковый номер текущей записи, считая от начала обработки исходных данных.

OFMT

Формат вывода чисел, по умолчанию %.6g.

OFS

Разделитель полей при выводе, по умолчанию - пробел.

ORS

Разделитель записей при выводе, по умолчанию - перевод строки.

RLENGTH

Длина успешно сопоставленной функцией match() цепочки символов.

RS

Первым символом цепочки, представляющей собой значение переменной   RS, является разделитель исходных записей (по умолчанию - перевод строки). Если значение RS пусто, между записями может располагаться несколько пустых строк.

RSTART

Начальная позиция успешно сопоставленной функцией match()   цепочки символов (считая от 1).

SUBSEP

Цепочка символов - разделитель индексов многомерных массивов; подразумеваемое значение зависит от реализации.

Приведем примеры использования утилиты awk. Сложить числа, стоящие в первом столбце исходного файла, вывести сумму и среднее арифметическое позволяет awk-программа, показанная в пример 6.26.



{ s += $1 } END { print "Сумма:", s, " Среднее арифметическое:", s/NR }

Листинг 6.26. Пример awk-программы, оперирующей с числами.

Командная строка из пример 6.27, служит для вывода тех строк файла f1.txt, у которых первое поле не совпадает с первым полем предыдущей строки.

awk '$1 != prev { print; prev = $1 }' f1.txt

Листинг 6.27. Пример awk-программы, заданной в командной строке.

Чтобы распечатать файл f2.txt, вставляя после слова "Page" номера страниц (начиная с первой), можно воспользоваться awk-программой (предполагается, что она помещена в файл prog.awk) и командной строкой, представленными, соответственно, в листингах пример 6.28 и пример 6.29.

/Page/ { $2 = n++ } { print }

Листинг 6.28. Пример awk-программы, использующей шаблоны.

awk -f prog.awk -v 'n=1' f2.txt

Листинг 6.29. Пример вызова awk-программы, использующей шаблоны.

Программа, показанная в пример 6.30, выводит поля входных записей, по одному на строке.

{ for (i = NF; i > 0; --i) print $i }

Листинг 6.30. Пример awk-программы, использующей оператор цикла.

Промоделировать работу утилиты echo можно с помощью awk-программы (см. пример 6.31).

BEGIN { for (i = 1; i < ARGC; ++i) printf ("%s%s", ARGV [i], i == ARGC - 1 ? "\n" : " ") }

Листинг 6.31. Пример awk-программы, использующей оператор цикла и специальные переменные awk.

Следующая awk-программа (см. пример 6.32) позволяет разложить список поиска, хранящийся в переменной окружения PATH, по элементам массива.

BEGIN { n = split (ENVIRON ["PATH"], path, ":") for (i = 1; i <= n; ++i) print path [i] }

Листинг 6.32. Пример awk-программы, использующей встроенную функцию split().

В пример 6.33 приведен фрагмент командного файла, выполняемого при выключении системы. Здесь можно обратить внимание на разные виды экранирования. (Третье поле в выдаче mount - это точка монтирования.)

# Перемонтируем на чтение все, что еще остается смонтированным. mount | awk '/( \/ |^\/dev\/root)/ { print $3 }' | while read line; do mount -n -o ro,remount $line done



Листинг 6.33. Пример использования утилиты awk в системном командном файле.

Отметим, что в POSIX- 2001 стандартизована весьма развитая версия awk, входной язык которой приближен к языку C.

На уровне функций работа с регулярными выражениями поддержана семейством regex (см. пример 6.34).

#include <regex.h> int regcomp (regex_t *restrict preg, const char *restrict pattern, int cflags); int regexec (const regex_t *restrict preg, const char *restrict string, size_t nmatch, regmatch_t pmatch [restrict], int eflags); void regfree (regex_t *preg); size_t regerror (int errcode, const regex_t *restrict preg, char *restrict errbuf, size_t errbuf_size);

Листинг 6.34. Описание функций семейства regex().

Первый член этого семейства, функция regcomp(), компилирует регулярное выражение, заданное аргументом pattern, и помещает результат компиляции в структуру типа regex_t, на которую указывает аргумент preg. Эта структура, описанная в заголовочном файле <regex.h>, должна содержать про крайней мере поле

size_t re_nsub; /* Число заключенных в скобки подвыражений */

Третий аргумент функции regcomp(), cflags, задается как побитное ИЛИ следующих флагов:

REG_EXTENDED

Использовать расширенные регулярные выражения (подразумеваемый тип регулярных выражений - базовые).

REG_ICASE

При сопоставлении не различать большие и малые буквы.

REG_NOSUB

В regexec() сообщать только об успехе/неудаче сопоставления (и не устанавливать значения поля   re_nsub структуры regex_t).

REG_NEWLINE

Изменить трактовку переводов строк (мы не будем на этом останавливаться).

Функция regexec() сопоставляет цепочку символов   string со скомпилированным шаблоном, заданным аргументом preg. При успешном выполнении результат равен нулю; в противном случае возвращается ненулевое значение, свидетельствующее о неудаче сопоставления или ошибке. Аргумент eflags - побитное ИЛИ флагов REG_NOTBOL и REG_NOTEOL - определяет, являются ли границы цепочки границами строки, что важно для обработки фиксаторов ^ и $.


Если значение аргумента nmatch равно нулю или при вызове regcomp() был задан флаг REG_NOSUB, аргумент pmatch функции regexec() игнорируется. В противном случае он должен указывать на массив не менее чем из nmatch элементов, который будет заполнен смещениями подцепочек, сопоставленных с заключенными в скобки подвыражениями шаблона (pmatch [0] соответствует всему регулярному выражению, в неиспользуемые элементы помещается -1).

Структурный тип regmatch_t должен включать по крайней мере следующие поля:

regoff_t rm_so; /* Смещение в байтах начала подцепочки от начала цепочки */

regoff_t rm_eo; /* Смещение в байтах первого символа за концом подцепочки от начала цепочки */

Тип regoff_t определяется как целое со знаком, способное вместить любое значение типов off_t и ssize_t.

Функция regfree() освобождает память, запрошенную вызовом regcomp() с тем же значением аргумента preg, которое после этого нельзя использовать как указатель на скомпилированное регулярное выражение.

В файле <regex.h> определены константы, возвращаемые функциями семейства regex() в случае ошибки. Например, значение REG_NOMATCH возвращается функцией regexec() при неудаче сопоставления, REG_BADPAT обозначает некорректное регулярное выражение, REG_ESPACE - нехватку памяти и т.д. Функция regerror() отображает эти константы в неспецифицируемые стандартом цепочки печатных символов и помещает их в буфер errbuf. Приложение, вызывая regerror(), должно передать в качестве аргумента errcode последнее ненулевое значение, возвращенное функциями regcomp() или regexec() с заданным значением аргумента preg.

Приведем пример использования функций семейства regex() (см. пример 6.35). Обратим внимание на задание флага REG_NOTBOL при повторных обращениях к regexec().

#include <stdio.h> #include <limits.h> #include <regex.h>

/* Программа ищет все вхождения заданного шаблона во всех входных строках */ /* и выводит успешно сопоставленные подцепочки */

#define PATTERN "[A-Za-z][A-Za-z0-9]{0,31}"



int main (void) { char line [LINE_MAX]; /* Буфер для входных строк */ char *pline; /* Указатель на начало сопоставляемой части строки */ regex_t cere; /* Скомпилированное расширенное регулярное выражение */ regmatch_t pm; /* Структура для запоминания границ сопоставленной подцепочки */ int reerrcode; /* Код ошибки от regcomp или regexec */ char reerrbuf [LINE_MAX]; /* Буфер для строк с сообщениями об ошибках */ int i;

if ((reerrcode = regcomp (&cere, PATTERN, REG_EXTENDED)) != 0) { (void) regerror (reerrcode, &cere, reerrbuf, sizeof (reerrbuf)); fputs (reerrbuf, stderr); fputc ('\n', stderr); regfree (&cere); return (reerrcode); }

fputs ("Вводите строки, сопоставляемые с шаблоном " PATTERN "\n", stdout); while (fgets (line, sizeof (line), stdin) != NULL) { /* Произведем первое сопоставление с прочитанной строкой. */ /* Оно отличается от остальных при наличии в шаблоне фиксатора начала */ reerrcode = regexec (&cere, pline = line, 1, &pm, 0); while (reerrcode == 0) { /* Повторяем, пока сопоставления с остатком строки успешны */ fputs ("Сопоставленная подцепочка: ", stdout); for (pline += pm.rm_so, i = pm.rm_eo - pm.rm_so; i-- > 0; ) { fputc (*pline++, stdout); } fputc ('\n', stdout); reerrcode = regexec (&cere, pline, 1, &pm, REG_NOTBOL); } }

regfree (&cere); return (ferror (stdin) || ferror (stdout)); }

Листинг 6.35. Пример использования функций семейства regex().

Читателю рекомендуется поэкспериментировать с шаблоном PATTERN, пробуя различные варианты, в том числе некорректные.

Идейно простым, но весьма мощным и полезным средством обработки текстовых файлов является служебная программа преобразования символов tr:

tr [-c | -C] [-s] цепочка1 цепочка2 tr -s [-c | -C] цепочка1 tr -d [-c | -C] цепочка1 tr -ds [-c | -C] цепочка1 цепочка2

Она не применяет регулярных выражений как таковых, но в цепочках - аргументах можно употреблять конструкции, аналогичные некоторым односимвольным РВ.

Утилита tr копирует стандартный ввод на стандартный вывод с заменой либо удалением выбранных символов.


При отсутствии опций введенные символы, найденные в цепочке1, заменяются на соответствующие (стоящие на тех же относительных позициях) символы из цепочки2. Опции -c и -C предписывают использовать вместо цепочки1 ее дополнение до множества всех символов; в первом случае дополнение упорядочивается в соответствии с кодировкой, во втором - по алфавиту. По опции -d будут удалены все входные символы, заданные цепочкой1. Опция -s задает сжатие (до одного) последовательностей одинаковых символов, специфицированных последней из цепочек, указанных в командной строке (сжатие производится после каждой замены и/или удаления).

Чтобы задавать в цепочках - аргументах утилиты tr группы символов, можно воспользоваться следующими конструкциями.

c1-c2

Обозначает цепочку символов, лежащих в диапазоне от c1 до c2 включительно.

[:класс_символов:]

Обозначает цепочку символов, принадлежащих указанному классу (см. выше описание базовых регулярных выражений).

[=класс_эквивалентности=]

Обозначает цепочку символов, принадлежащих указанному классу эквивалентности при алфавитном сравнении.

[c*n]

Обозначает символ c, повторенный n раз. Может использоваться только в цепочке2. Если первая цифра в n есть 0, n рассматривается как восьмеричное число; иначе - как десятичное. Нулевое или отсутствующее n воспринимается как "очень много"; эта возможность полезна при дополнении цепочки2 до длины цепочки1.

Обратный слэш можно использовать для задания управляющих символов ('\\', '\a', '\b', '\f', '\n', '\r', '\t', '\v'). Кроме того, \ обозначает код символа, если за ним идут одна, две или три восьмеричные цифры.

Следующая команда (см. пример 6.36) помещает список всех слов из файла f1, по одному на строку, в файл f2 (под словом понимается максимальная последовательность букв).

tr -cs '[:alpha:]' '[\n*]' < f1 > f2

Листинг 6.36. Пример использования служебной программы tr.

Команда, показанная в пример 6.37, переводит большие буквы в малые, попутно сжимая последовательности одинаковых (без учета регистра) букв.



tr -s '[:upper:]' '[:lower:]'

Листинг 6.37. Пример трансляции и сжатия последовательностей символов с использованием служебной программы tr.

Служебная программа uniq

uniq [-c | -d | -u] [-f число] [-s число] [входной_файл [выходной_файл]]

позволяет сократить до одной подряд идущие одинаковые строки (сделать одинаковые строки файла смежными можно с помощью утилиты sort). Опции предоставляют дополнительный сервис.

-c

Перед каждой выходной строкой помещать ее кратность во входном файле.

-d

Подавить вывод неповторяющихся строк.

-f число

При сравнении строк игнорировать заданное число начальных полей. Поле определяется как максимальная цепочка символов, успешно сопоставляемая с базовым регулярным выражением   [[:blank:]]*[^[:blank:]]*.

-s число

При сравнении строк игнорировать заданное число начальных символов. При совместном использовании опций -f и -c игнорируется указанное число символов, идущих после заданного числа полей.

-u

Подавить вывод строк, повторявшихся во входном файле.

В качестве примера употребления утилиты uniq приведем конвейер, позволяющий найти десять самых употребительных заголовочных файлов среди включаемых в стандартные заголовочные файлы, расположенные в каталоге   /usr/include и его подкаталогах (см. пример 6.38). Результат работы конвейера может выглядеть так, как показано в пример 6.39.

find /usr/include -name '*.h' -exec cat {} \; | tr -d '[:blank:]' | \ grep -E -e '^#include(<.*>|".*")' | sort | uniq -dc | sort -r | head

Листинг 6.38. Пример использования служебной программы uniq.

977 #include"nsISupports.h" 315 #include<glib.h> 201 #include<gdk/gdk.h> 167 #include<glibmm.h> 160 #include<features.h> 154 #include<glib-object.h> 144 #include"nsCOMPtr.h" 139 #include<sys/types.h> 139 #include<glibmm/class.h> 135 #include"nscore.h"

Листинг 6.39. Возможный результат работы конвейера, показанного в листинге 6.38.


Содержание раздела