Регулярные выражения
Спецификаторы формата для pack и unpack – достаточно простой способ записи, описывающий компоновку пакетов. Следующая тема iniero обсуждения – несколько более сложный, но гораздо более выразительный способ записи – регулярные выражения (regular pressions), определяющие шаблоны текста (patterns of text). В этой книге мы время от времени использовали регулярные выражения, не вая им точного описания; они достаточно знакомы, чтобы понять их без особых пояснений. Хотя регулярные выражения широко распространены в средах программирования под Unix, в других системах они применяются гораздо реже, поэтому в данном разделе мы решили показать некоторые их преимущества. На случай, если у вас нет под рукой библиотеки с регулярными выражениями, мы приведем ее простейшую реализацию.
Существует несколько разновидностей регулярных выражений, но суть у них у всех одинакова – это способ описания шаблона буквенных символов, включающего в себя повторения, альтернативы и сокращения для отдельных классов символов вроде цифр или букв. Примером такого шаблона могут служить всем знакомые символы замещения (wildcards), используемые в процессорах командной строки или оболочках для задания образцов поиска имен файлов. Как правило, символ * используется для обозначения "любой строки символов", так что команда:
С:\> del *.exe
Использует шаблон, которому соответствуют все имена файлов, оканчивающиеся на ".ехе". Как это часто бывает, детали меняются от системы к системе и даже от программы к программе.
Причуды отдельных программ могут навести на мысль, что регулярные выражения являются механизмом? создаваемым adhoc, для каждого случая отдельно, но на самом деле регулярные выражения – это язык с формальной грамматикой и точным значением каждого выражения. Более того, при должной реализации конструкция работает очень быстро: благодаря соединению теории и практического опыта; вот пример в пользу специализированных алгоритмов, упоминавшихся нами в главе 2.
Регулярное выражение есть последовательность символов, которая определяет множество образцов поиска. Большинство символов просто-напросто соответствуют сами себе, так что регулярное выражение abc будет соответствовать именно этой строке символов, где бы она ни появлялась. Но, кроме того, существует несколько метасимволов, которые обозначают повторение, группировку или местоположение. В принятых в Unix регулярных выражениях символ ~ обозначает начало строки, а $ – конец строки, так что шаблон ~х соответствует только символу х в начале строки, х$ – только х в конце строки. Шаблону ~х$ соответствует только строка, содержащая единственный символ х, и, наконец, ~$ соответствует пустая строка.
Символ. (точка) обозначает любой символ, так что выражению х. у будут соответствовать хау, х2у и т. п., но не xy или xaby; выражению ~. $ соответствует строка из одного произвольного символа.
Набору символов внутри квадратных скобок соответствует любой из этих символов. Таким образом, [0123456789] соответствует одной цифре, это выражение можно записать и в сокращенном виде: [0-9]1.
Эти строительные блоки комбинируются с помощью круглых скобок () для группировки, символа | – для обозначения альтернатив, * – для обозначения нуля или более совпадений, + – для обозначения одного или более совпадений и ? – для обозначения нуля или одного совпадения. Наконец, символ \ применяется в качестве префикса перед метасимволом для исключения его специального использования, то есть \* обозначает просто символ *, а \\ – собственно символ обратной косой черты \.
Наиболее известным инструментом работы с регулярными выражениями является программа grep, о которой мы уже несколько раз упоминали. Эта программа – чудесный пример, показывающий важность нотации. В ней регулярное выражение применяется к каждой строке вводимого файла и выводятся строки, которые содержат образцы поиска, описываемые этим выражением. Эта простая спецификация с помощью регулярных выражений позволяет справиться с большим количеством ежедневной рутинной работы. В приведенных ниже примерах обратите внимание на то, что синтаксис регулярных выражений, используемых в качестве аргументов grep, отличается от шаблонов поиска, применяемых для задания набора имен файлов; различие обусловлено разным назначением выражений.
Какой исходный файл использует класс Regexp?
% grep Regexp *.java
В каком файле этот класс реализован?
% grep 'class.*Regexp' *.java
Куда я подевал это письмо от Боба?
% grep '"From:.* bob@' mail/*
Сколько непустых строк кода в этой программе?
grep *. с++ 1 we