Вводная часть - знакомство с регулярными выражениями
Назначение и использование регулярных выражений
Регулярные выражения используются для расширенного контекстного поиска и модификации текста. Поддержка регулярных выражения реализована во многих текстовых редакторах, таких как vi, ex и emacs, в программах поиска текста grep/egrep, в потоковых редакторах sed и awk, в языках программирования Perl, PHP, Pyton, Java и т.д. Они широко применяются в программах синтаксического анализа (parser programs).
Диалекты регулярных выражений
Не существует единого стандарта для реализации поддержки регулярных выражений теми или иными языковыми платформами и средствами обработки текста. Но можно выделить некоторые базовые и расширенные возможности. Например, два типа программы grep: grep и egrep, используют регулярные выражения с незначительно отличающимися возможностями. Perl, возможно, имеет более полный набор регулярных выражений. Эти отличия в реализации принято называть диалектами регулярных выражений. К счастью, все они следуют общим принципам. Как только вы поймете основную идею, то вам будет легко изучить детали различных диалектов.
Аналогия с файловыми шаблонами
Для начала можно провести аналогию между регулярными выражениями и файловыми шаблонами. Так, например, все текстовые файлы могут быть поименованы с использованием в качестве общей части имени суффикса txt. Тогда, для вывода на экран имен всех текстовых файлов можно воспользоваться файловым шаблоном "*.txt". Для командного интерпретатора (Shell) символ "*" означает любую последовательность символов, а символ "?" - один произвольный символ. Таким образом шаблон "*.txt" означает: выбрать все файлы, имена которых начинаются с любой последовательности символов и заканчиваются символами .txt .
Регулярное выражение состоит из двух типов символов. Специальные символы (вроде * в файловых шаблонах) называются метасимволами. Все остальные символы, то есть обычный текст, называются литералами. Регулярные выражения отличаются от файловых шаблонов в первую очередь гораздо большим разнообразием используемых метасимволов.
Регулярное выражение - это формальное описание шаблона, который соответствует текстовой строке.
Простые регулярные выражения
Рассмотрим простой пример. Пусть вы имеете список телефонов компании и он выглядит подобно этому:
Phone Name ID
...
...
3412 Bob 123
3834 Jonny 333
1248 Kate 634
1423 Tony 567
2567 Peter 435
3567 Alice 535
1548 Kerry 534
...
Это компания, скажем, из 500 человек. Данные хранятся в простом текстовом файле по имени phonelist.txt. Человек с первой цифрой телефонного номера равной 1 работает в строении номер 1. Как получить все записи о работающих в строении номер 1?
Вот примеры регулярных выражений, дающих ответ на этот вопрос:
grep '^1' phonelist.txt
или
egrep '^1' phonelist.txt
или
perl -ne 'print if (/^1/)' phonelist.txt
На словах это значит: "искать все строки, которые начинаются с единицы". Символ "^" (крыша домиком, циркумфлекс) соответствует началу строки. Благодаря этому символу выражение соответствует только тем строкам, в которых сразу после позиции начала строки идет символ "единица" - 1.
Наиболее часто используемым и простым является регулярное выражение, состоящее только из литералов. Примером такого регулярного выражения является символ "1" в предыдущем примере. Он просто соответствует единице в тексте.
Другой пример:
egrep 'Kerry' phonelist.txt
Это регулярное выражение состоит только из литералов (буквы K,e...).
Символьные классы
Символы могут быть сгруппированы вместе в класс. Класс представляется как пара из открывающей и закрывающей квадратных скобок и списка символов между ними. Такая конструкция называется "символьным классом" и используется для перечисления всех возможных символов, которые могут находиться в данной позиции текста. Один и только один из этих символов должен присутствовать в тексте, который совпадет с шаблоном. Например:
[abc] - регулярное выражение, которое совпадает с какой-либо из букв a, b или c.
[ab0-9] - регулярное выражение, которое соответствует либо a, либо b, либо цифре в интервале от нуля до девяти.
[a-zA-Z0-9\-] - соответствует букве в верхнем или нижнем регистре, цифре или знаку минус.
Попробуем сделать следующее:
egrep '^1[348]' phonelist.txt
Эта команда выдаст строки, которые начинаются с 13 или 14 или 18. Мы уже увидели, что большинство ASCII символов просто соответствуют этим же ASCII символам, но некоторые ASCII символы, называемые метасимволами, имеют специальное значение. Например, квадратные скобки ограничивают класс. Смысл "-" в классе это интервал. Чтобы отменить специальное значение метасимвола, нужно поставить перед ним обратный слеш. Пример этого - знак минус в [a-zA-Z0-9\-]. В некоторых диалектах регулярных выражений метасимволы начинаются с обратного слеша. В этом случае вы должны удалить обратный слеш, чтобы получить нормальное значение символа.
Один произвольный символ
Точка - это важный метасимвол. Она соответствует любому символу, кроме символа новой строки. Пример:
grep '^.2' phonelist.txt
или
egrep '^.2' phonelist.txt
Эти выражения используются для поиска строк с цифрой 2 во второй позиции и любым первым символом.
Инвертированные символьные классы
Если определение класса начинается с "[^" вместо "[", то это означает отрицание этого класса (все символы кроме указанных). Теперь "^" больше не означает начало строки, но в комбинации с "[" обозначает отрицание класса.
[^0-9] - соответствует любому одному НЕ цифровому символу.
[^abc] - соответствует любому символу, который не a, b или c.
. - точка соответствует любому символу, исключая символ новой строки.
Это то же самое, как [^\n], где \n - символ новой строки.
Для нахождения всех строк, которые не начинаются с 1, мы можем написать:
grep '^[^1]' phonelist.txt
или
egrep '^[^1]' phonelist.txt
“Якорные” метасимволы
В предыдущей части мы уже увидели, что "^" соответствует началу строки. Фиксирование или привязка регулярного выражения к заданной позиции строки производится при помощи специальных символов, которые соответствуют определенной позиции в тексте, а не просто символу.
^ - соответствует началу строки.
$ - соответствует концу строки.
Чтобы найти человека с ID номер 567 в нашем phonelist.txt, мы можем использовать следующие команды:
egrep '567$' phonelist.txt
В результате мы получим строки с числом 567 в конце.
Квантификаторы или множители
Квантификаторы (множители) определяют, сколько раз регулярное выражение повторяется в тексте.
Пример из телефонного списка: ....
1248 Kate 634
....
1548 Kerry 534
....
Для нахождения строк, которые начинаются с 1 и имеют несколько цифр, как минимум один пробел и имя, начинающееся с буквы К, мы можем написать:
grep '^1[0-9]\{1,\} \{1,\}K' phonelist.txt
или использовать * и повторить [0-9] и пробел:
grep '^1[0-9][0-9]* *K' phonelist.txt
или
egrep '^1[0-9]+ +K' phonelist.txt
или
perl -ne 'print if (/^1[0-9]+ +K/)' phonelist.txt
Множитель относится к ближайшему правильному регулярному выражению. Так, "23*4" не означает "2, затем 3, затем что угодно и 4" (это выглядит как "23.*4"). А это означает "один раз 2, потом может быть несколько (или ни одной) 3 и одна 4".
Также важно учесть, что множители обладают жадностью. Это означает, что действие первого множителя в регулярном выражение распространяется направо так далеко, как это возможно. Так, например, выражение ^1.*4 будет соответствовать всей строке 1548 Kerry 534 с начала и до самой последней 4.
Т.е. оно соответствует не только 154, а всей строке. Это не имеет большого значения для grep, но важно для редактирования и подстановки.
Круглые скобки как способ запоминания
Круглые скобки не изменяют действие выражения, при этом просто запоминается ограниченная скобками часть текста для того, чтобы можно было ссылаться на нее в выражении позднее.
Часть, которая была запомнена, доступна через переменные. Часть строки, ограниченная первой парой скобок, доступна через переменную один, вторая - через переменную два, и т.д.
Пример: выражение [a-z][a-z] будет соответствовать двум буквам в нижнем регистре. Теперь мы можем использовать эти переменные для создания шаблонов для поиска текста такого как 'otto' (зеркальные имена):
egrep '([a-z])([a-z])\2\1'
Переменная \1 содержит букву о, и переменная \2 - букву t. Результат также будет содержать имя anna, но не olol.
Круглые скобки как способ запоминания не так часто используются для нахождения таких имен, как otto и anna, а применяются для редактирования и подстановки.
Использование регулярных выражений для редактирования текста
Для редактирования c использованием регулярных выражений вам необходим редактор, такой, как vi, emacs, или вы можете использовать, например, perl.
В emacs вы используете комбинацию Alt-X и команды query-replace-regexp или можете для этого переопределить любую функциональную клавишу. Также можете использовать команду replace-regexp. Команда query-replace-regexp интерактивная, другие нет.
В vi используется команда подстановки ':%s/ / /gc'. Процент определяет диапазон 'весь файл' и может быть заменен любым подходящим диапазоном. В vim вы нажимаете shift-v, помечаете область и тогда используете подстановку только в этой области. Довольно много написано в собственном руководстве к vim. Интерактивная версия команды - это 's/ / /gc', а не интерактивная - это 's/ / /g'. Интерактивность означает, что вы должны будете подтвердить, выполнять или нет подстановку в каждом случае.
В Perl'е вы можете использовать команду:
perl -pe 's/ / /g'
Посмотрим несколько примеров. План нумерации в нашей компании изменился, и во всех телефонных номерах, которые начинаются с 1, необходимо вставить 2 после второй цифры. Например, 1423 должно стать 14223.
Старый список:
Phone Name ID
...
3412 Bob 123
3834 Jonny 333
1248 Kate 634
1423 Tony 567
2567 Peter 435
3567 Alice 535
1548 Kerry 534
...
Как сделать это? Даем команду:
- в vi: s/^\(1.\)/\12/g
- в perl: perl -pe 's/^(1.)/${1}2/g' phonelist.txt.
Теперь новый список телефонов выглядит подобно этому:
Phone Name ID ...
3412 Bob 123
3834 Jonny 333
12248 Kate 634
14223 Tony 567
2567 Peter 435
3567 Alice 535
15248 Kerry 534
...
Перл может обрабатывать переменные с номерами больше, чем от \1 до \9, поэтому \12 будет ссылаться на 12-ю переменную, которая, кстати, пустая. Для преодоления этого мы просто используем ${1}.
Теперь выравнивание в списке немного нарушено. Как вы можете это исправить? Вы должны просто проверить, есть ли пробел в 5-й позиции, и если есть, то вставить еще один:
- в vi: s/^\(....\) /\1 /g
- в perl: perl -pe 's/^(....) /${1} /g' phonelist.txt
Теперь телефонный список будет выровненным.
Коллеги вручную подредактировали список и случайно вставили несколько пробелов в начале некоторых строк. Как мы можем удалить их? Следующая команда должна помочь:
- в vi: s/^ *// (здесь 2 пробела, т.к. мы не имеем +).
- в perl: perl -pe 's/^ +//' phonelist.txt.