headermask image


Advertisement

LPI 101: GNU и UNIX команды. Поиск с помощью регулярных выражений

Регулярные выражения впервые появились в теории компьютерных языков. Большинство студентов по computer science учат, что язык, описываемый регулярными выражениями, в точности такой, какой принимает конечный автомат. Регулярные выражения в этом разделе могут нести более сложный смысл, поэтому они не в точности такие же, какие вы изучали на занятиях по информатике, хотя у них одинаковое родство.

Регулярные выражения (также называемые как “regex” или “regexp”) представляют способ описания текстовой строки или шаблона таким образом, что программа может осуществлять соответствие шаблона в произвольных текстовых строках, обеспечивая мощные инструменты поиска информации. Утилита grep (от generalized regular expression processor) является стандартной частью инструментария программиста или администратора Linux или UNIX, позволяя использовать регулярные выражения для поиска файлов или вывода команды. В разделе о текстовых потоках и фильтрах мы познакомились с sed или stream editor, который является еще одним стандартным инструментом, использующим регулярные выражения для поиска и замены текста в файлах или текстовых потоках. Этот раздел поможет лучше понять использование регулярных выражений в grep и sed. Другой программой, использующей регулярные выражения, является awk, которая входит в материал экзамена 201 на сертификацию LPIC-2. Как и по остальным темам этого руководства, по регулярным выражениям и теории языков написано много книг.

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

Основные строительные блоки

В программе GNU grep, которую можно найти на большинстве Linux систем, используется два синтаксиса регулярных выражений: основной и расширенный. У программы GNU grep нет никаких функциональных отличий. Здесь описан основной синтаксис, а также отличие его от расширенного синтаксиса.

Регулярные выражения состоят из символов и операторов, дополненных метасимволами. Большинство символов соответствует своим значениям, а большинство метасимволов необходимо предварять обратным слешем (\). Основными операторами являются

Конкатенация
Конкатенация соединяет два регулярных выражения. Например, регулярное выражение a найдет соответствие в строке abcdcba дважды (первый и последний символ a) точно также и с регулярным выражением b. Однако ab соответствует только abcdcba, в то время как ba соответствует только abcdcba.
Повторение
Оператор Клини * или повторения соответствует нулю или более появлений предшествующего регулярного выражения. Поэтому выражение a*b соответствует любой строке из a, завершенной символом b включая самой строке из символа b. Оператор Клини * не надо записывать как escape-последовательность, но в выражении, если вы хотите найти сам символ (*) необходимо писать escape-последовательность.
Выбор
Оператор выбора (|) соответствует либо предшествующему, либо последующему выражению. Его надо писать, как escape-последовательность, если используется основной синтаксис. Например, выражение a*\|b*c найдет строку из любого числа a или любого числа букв b (но не оба), завершенную символом c. Снова простой символ c соответствует такому выражению.

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

В качестве примеров мы будем использовать текстовые файлы, созданные ранее в каталоге lpi103. Рассмотрите простые примеры в Листинге 107. Заметим, что grep принимает регулярное выражение как обязательный параметр и ноль и более файлов, в которых надо осуществить поиск. Если никаких файлов не указано, то grep будет искать в stdin, что делает ее похожей на фильтр, который можно использовать в конвейере. Если соответствия не найдено, то вывода grep не будет, хотя можно проверить ее код выхода.

Листинг 107. Простые регулярные выражения

[ian@echidna lpi103]$ grep p text1
1 apple
2 pear
[ian@echidna lpi103]$ grep pea text1
2 pear
[ian@echidna lpi103]$ grep "p*" text1
1 apple
2 pear
3 banana
[ian@echidna lpi103]$ grep "pp*" text1
1 apple
2 pear
[ian@echidna lpi103]$ grep "x" text1
[ian@echidna lpi103]$ grep "x*" text1
1 apple
2 pear
3 banana
[ian@echidna lpi103]$ cat text1 | grep "l\|n"
1 apple
3 banana
[ian@echidna lpi103]$ echo -e "find an\n* here" | grep "\*"
* here

Глядя на приведенные примеры, вы иногда можете удивиться полученным результатам, особенно при использовании повторения. Вы, возможно, ожидали, что p* или, по крайней мере pp* соответствует паре символов p, но p* и x* тоже соответствует каждой строке файла, так как оператор * соответствует нулю или большему числу раз предшествующего регулярного выражения.

Первые ярлыки

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

+
Оператор + похож на оператор *, за исключением того, что он соответствует одному или более вхождению предыдущего регулярного выражения. В основном синтаксисе его необходимо писать как escape-последовательность.
?
Символ ? означает ноль или более вхождений предыдущего выражения. Это не тот же знак ?, используемый при подстановках.
.
Метасимвол . (точка) означает любой символ. Одним из наиболее часто используемых шаблонов является .*, который соответствует строке произвольной длины из любых символов (или не содержащей символов совсем). Сравните точку со знаком ?, используемым в подстановке и .* с *, используемым в подстановке.

Листинг 108. Регулярные выражения

[ian@echidna lpi103]$ grep "pp\+" text1 # at least to p's
1 apple
[ian@echidna lpi103]$ grep "pl\?e" text1
1 apple
2 pear
[ian@echidna lpi103]$ grep "pl\?e" text1 # pe with optional l between
1 apple
2 pear
[ian@echidna lpi103]$ grep "p.*r" text1 # p, some string then r
2 pear
[ian@echidna lpi103]$ grep "a.." text1 # a followed by two other letters
1 apple
3 banana

Соответствие начала или конца строки

Знак ^ (каретки) означает начало строки, в то время как $ (знак доллара) означает конец строки. Поэтому ^..b соответствует любым двум символам в начале строки, за которыми следует b, в то время как ar$ соответствует любой строке, заканчивающейся на ar. Регулярное выражение ^$ соответствует пустой строке.

Более сложные выражения

До сих пор мы рассматривали применение повторения к одному символу. Если вы хотите найти одно или более вхождений строки из нескольких символов как an, которая встречается дважды в banana, используйте круглые скобки, которые надо записывать как escape-последовательности в основном синтаксисе. Также вы можете захотеть осуществить поиск нескольких символов, не используя такой обобщенный оператор как . или длинную последовательность альтернатив. Это можно сделать, заключив альтернативы в квадратные скобки ([]), которые не надо писать как escape-последовательность при использовании основного синтаксиса. Выражения в квадратных скобках составляют класс символов.Кроме нескольких исключений, о которых мы поговорим позже, использование квадратных скобок позволяет обойтись без использования escape-последовательностей при использовании специальных символов, таких как as . и *.

Листинг 109. Круглые скобки и классы символов

[ian@echidna lpi103]$ grep "\(an\)\+" text1 # find at least 1 an
3 banana
[ian@echidna lpi103]$ grep "an\(an\)\+" text1 # find at least 2 an's
3 banana
[ian@echidna lpi103]$ grep "[3p]" text1 # find p or 3
1 apple
2 pear
3 banana
[ian@echidna lpi103]$ echo -e "find an\n* here\nsomewhere." | grep "[.*]"
* here
somewhere.

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

Диапазон выражения
Диапазон выражения это два символа, разделенных знаком – (дефис), как например 0-9 для цифр или 0-9a-fA-F для шестнадцатеричных чисел. Заметим, что диапазон зависит от локали.
Именованные классы
Несколько именованных классов обеспечивают удобное обозначение для часто используемых классов. Именованные классы начинаются с [: и заканчиваются :]. Некоторые примеры:

[:alnum:]
Цифровые и буквенные символы
[:blank:]
Пробелы и символы табуляции
[:digit:]
Цифры от 0 до 9 (эквивалентно от 0-9)
`[:upper:] и [:lower:]
Соответственно буквы верхнего и нижнего регистра.

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

Зная особые значения символов выше, делаем вывод, что если вы хотите обнаружить символ – (дефис) в классе символа, то должны помещать его первым или последним. Если вы хотите обнаружить символ ^ (каретка), то не ставьте его первым. Знак ] (правая квадратная скобка) закрывает класс, кроме случая, когда он стоит первым.

Классы символов — это та область, в которой регулярные выражения и подстановка ведут себя одинаково, хотя отрицание отличается (^ против !). В Листинге 108 приведены примеры классов символов.

Листинг 110. Классы символов

[ian@echidna lpi103]$ # Ищет символы от 3 до 7
[ian@echidna lpi103]$ echo -e "123\n456\n789\n0" | grep "[3-7]"
123
456
789
[ian@echidna lpi103]$ # Ищем цифру, за которой до конца строки нет букв n и r
[ian@echidna lpi103]$ grep "[[:digit:]][^nr]*$" text1
1 apple

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

В кратком введении в Sed упоминалось о том, что sed использует регулярные выражения. Regexp могут использоваться как в выражениях адресации, так и в выражениях подстановки. Так¸ выражение /abc/s/xyz/XYZ/g означает: применить подстановку команды, которая заменит на XYZ все вхождения xyz только в строках, содержащих abc. В Листинге 111 приведено два примера с файлом text1, а в другом мы заменяем последнее слово перед точкой (.) на строку LAST WORD. Заметим, что строка First не изменилась, так как не была предварена пробелом.

Листинг 111. Регулярные выражения в sed

[ian@echidna lpi103]$ sed -e '/\(a.*a\)\|\(p.*p\)/s/a/A/g' text1
1 Apple
2 pear
3 bAnAnA
[ian@echidna lpi103]$ sed -e '/^[^lmnXYZ]*$/s/ear/each/g' text1
1 apple
2 peach
3 banana
[ian@echidna lpi103]$ echo "First. A phrase. This is a sentence." |\
> sed -e 's/ [^ ]*\./ LAST WORD./g'
First. A LAST WORD. This is a LAST WORD.

Расширенные регулярные выражения

Расширенные регулярные выражения позволяют не писать escape-последовательности нескольких символов, которые приходилось предварять знаком \ в основном синтаксисе, включая круглые скобки, ‘?’, ‘+’, ‘|’, и ‘{‘. Это значит, что они должны быть записаны как escape-последовательность, только если вы хотите, чтобы они интерпретировались как символы. Вы можете использовать опцию -E (или –extended-regexp ) команды grep, чтобы сигнализировать об использовании расширенного синтаксиса регулярных выражений. Можно использовать альтернативу в виде egrep.Некоторые старые версии sed не поддерживают расширенные регулярные выражения. Если ваша версия sed поддерживает расширенные regexps, используйте опцию -r, чтобы сообщить sed, что вы будете использовать расширенный синтаксис. В Листинге 112 показан пример, рассмотренный ранее, с использованием расширенной версии egrep.

Листинг 112. Расширенные регулярные выражения

[ian@echidna lpi103]$ grep "an\(an\)\+" text1 # find at least 2 an's
3 banana
[ian@echidna lpi103]$ egrep "an(an)+" text1 # find at least 2 an's
3 banana

Поиск информации в файлах

Этот раздел завершает некоторые примеры мощных команд grep и find которые позволяют искать информацию в файловой системе. Снова, примеры довольно простые; мы используем файлы, созданные в каталоге lpi103 и его детях.

Для начала grep может искать сразу в нескольких файлах. Если вы добавите опцию -n, то она скажет номера найденных строк. Если вы просто хотите знать количество найденных строк, используйте опцию -c, а если вам нужен список файлов, в которых есть совпадения, используйте опцию -l. В Листинге 113 приведены некоторые примеры.

Листинг 113. Поиск в нескольких файлах

[ian@echidna lpi103]$ grep plum *
text2:9 plum
text6:9 plum
text6:9 plum
yaa:9 plum
[ian@echidna lpi103]$ grep -n banana text[1-4]
text1:3:3 banana
text2:2:3 banana
[ian@echidna lpi103]$ grep -c banana text[1-4]
text1:1
text2:1
text3:0
text4:0
[ian@echidna lpi103]$ grep -l pear *
ex-here.sh
nohup.out
text1
text5
text6
xaa

Наш последний пример использует команду find, чтобы найти все обычные файлы в текущем каталоге и его детях, а затем использовании xargs для передачи списка файлов команде grep, которая определит число появлений строки banana в каждом файле. Наконец, вывод фильтруется через другой экземпляр grep, на этот раз с опцией -v для поиска всех файлов, которые не содержат ни одного появления искомой строки.

Листинг 114. Поиск файлов, в которых есть хотя бы одно вхождение banana

[ian@echidna lpi103]$ find . -type f -print0| xargs -0 grep -c banana| grep -v ":0$"
./text1:1
./text2:1
./xab:1
./yaa:1
./text5:1
./text6:4
./backup/text1.bkp.2:1
./backup/text1.bkp.1:1

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

Взято с ibm developerworks

Комментарии

Your email is never published nor shared. Required fields are marked *

*
*