grep
Название grep в действительности произошло от фразы «global regular expression print» (глобальный поиск с помощью регулярного выражения и вывод), то есть, как видите, grep имеет некоторое отношение к регулярным выражениям. В сущности, grep просматривает текстовые файлы в поисках совпадений с указанным регулярным выражением и выводит в стандартный вывод все строки с такими совпадениями.
До сих пор, мы передавали программе grep фиксированные строки, например:
ls -l /usr/bin | grep zip
Эта команда выведет список всех файлов из каталога /usr/bin
, имена которых содержат подстроку zip.
Программа grep имеет следующий синтаксис:
grep [параметры] регулярное_выражение [файл...]
Параметры команды grep
Параметр | Описание | Описание |
---|---|---|
-i | --ignore-case | Игнорировать регистр символов. Требует не различать символы верхнего и нижнего регистров |
-v | --invert-match | Инвертировать критерий. Обычно grep выводит строки с совпадениями. Этот параметр заставляет grep выводить строки, не содержащие совпадений |
-c | --count | Вывести число совпадений (или «несовпадений») в присутствии параметра -v вместо самих текстовых строк |
-l | --files-with-matches | Вместо строк с совпадениями выводить только имена файлов с найденными строками |
-L | --files-without-match | Действует подобно параметру -l, но выводит только имена файлов, где не найдено ни одного совпадения |
-n | --line-number | В начале каждой строки с совпадением вывести ее номер в файле |
-h | --no-filename | Подавить вывод имен файлов при поиске по нескольким файлам |
Создадим несколько текстовых файлов, чтобы наше исследование grep стало более предметным:
ls /bin > dirlist-bin.txt ls /usr/bin/ > dirlist-usr-bin.txt ls /sbin > dirlist-sbin.txt ls /usr/sbin/ > dirlist-usr-sbin.txt ls dirlist* dirlist-bin.txt dirlist-sbin.txt dirlist-usr-bin.txt dirlist-usr-sbin.txt
Ниже показано, как выполнить простой поиск в нашем списке файлов:
grep bzip dirlist*.txt dirlist-bin.txt:bzip2 dirlist-bin.txt:bzip2recover dirlist-usr-bun.txt:bzip2 dirlist-usr-bun.txt:bzip2recover
В этом примере grep просматривает все перечисленные файлы в поисках строки bzip и находит четыре совпадения, в файлах dirlist-bin.txt
и dirlist-usr-bun.txt
. Если бы нам достаточно было получить только имена файлов с совпадениями, а не сами совпадения, мы могли бы добавить параметр -l
grep -l bzip dirlist*.txt dirlist-bin.txt dirlist-usr-bin.txt
Напротив, получить список файлов, не содержащих совпадений, можно так:
grep -L bzip dirlist*.txt dirlist-sbin.txt dirlist-usr-sbin.txt
Метасимволы и литералы
Несмотря на то, что пока это не очевидно, во всех своих попытках поиска с помощью grep мы использовали регулярные выражения, хотя и очень простые. Регулярное выражение bzip, к примеру, означает, что ему соответствуют только строки в файлах, содержащие не менее четырех символов, и среди этих символов присутствуют символы b
, z
, i
и p
, следующие именно в таком порядке, и между ними отсутствуют какие-либо другие символы. Символы в строке bzip – это литеральные символы, то есть они соответствуют сами себе. Помимо литералов регулярные выражения могут содержать метасимволы, они используются для определения более сложных критериев сопоставления. К метасимволам регулярных выражений относятся следующие символы:
^ $ . [ ] { } - ? * + ( ) | \
Все остальные символы считаются литералами. Впрочем, в некоторых случаях символ «обратный слеш» используется для создания метапоследовательностей, а также для экранирования метасимволов, чтобы они могли интерпретироваться
как литералы, а не как метасимволы.
Любой символ
Первый метасимвол, который мы рассмотрим, — это символ «точка», соответствующий любому символу. Если включить его в регулярное выражение, он будет соответствовать любому символу в данной позиции. Например:
grep -h '.zip' dirlist*.txt bunzip2 bzip2 bzip2recover funzip gpg-zip gunzip gzip unzip unzipsfx bunzip2 bzip2 bzip2recover ...
Здесь выполнен поиск в наших файлах совпадений с регулярным выражением .zip. В полученных результатах имеется пара важных моментов, которые необходимо отметить. Обратите внимание, что программа zip не была найдена. Это
объясняется включением в регулярное выражение метасимвола «точка», увеличившим длину обязательного совпадения до четырех символов; так как в имени программы zip всего три символа, оно не было найдено. Кроме того, если бы в наших списках имелись имена файлов с расширением .zip
, они также были бы найдены, потому что символ «точка» в расширении файла интерпретировался бы как «любой символ».
Якоря
Символ крышки ^
и знак доллара $
в регулярных выражениях интерпретируются как якоря. Это означает, что в их присутствии совпадение с регулярным выражением возможно, только если совпадение будет найдено в начале строки ^
или в ее конце $
grep -h '^zip' dirlist*.txt zip zipcloak zipdetails zipgrep zipinfo zipnote zipsplit
grep -h 'zip$' dirlist*.txt funzip gpg-zip gunzip gzip unzip zip
Здесь выполняется поиск в списке файлов строки zip, находящейся в начале строки, в конце строки и занимающей всю строку, от начала до конца. Обратите внимание, что регулярное выражение ^$
(начало и конец без каких-либо символов между ними) будет соответствовать пустым строкам.
В директории /usr/share/dict
содержатся словари. Этим можно воспользоваться для решения кроссвордов
grep -i '^..j.r$' /usr/share/dict/words.txt major
Выражения в квадратных скобках и классы символов
В дополнение к возможности описать в регулярном выражении совпадение с любым символом в заданной позиции, используя выражения в квадратных скобках, можно также описать совпадение с одним символом из определенного множества.
Выражение в квадратных скобках помогает определить множество символов (включая символы, которые иначе интерпретировались бы как метасимволы), которые находятся в данной позиции. В следующем примере используется множество из двух символов, благодаря которому обнаруживаются соответствия с последовательностями bzip и gzip:
grep -h '[bg]zip' dirlist*.txt bzip2 bzip2recover gzip
Множество может содержать любое число символов. Метасимволы, заключенные в квадратные скобки, теряют свое специальное значение. Лишь два метасимвола интерпретируются особым образом, но при этом они имеют иной смысл. Первый – символ крышки ^
, который используется для обозначения отрицания; второй – дефис -
, который используется для обозначения диапазона символов.
Отрицание
Если сразу после открывающей квадратной скобки стоит символ крышки ^
, остальные символы множества интерпретируются как недопустимые в данной позиции. Проверим это, изменив предыдущий пример:
grep -h '[^bg]zip' dirlist*.txt bunzip2 funzip gpg-zip gunzip unzip unzipsfx bunzip2 funzip gpg-zip gunzip unzip unzipsfx
Включив отрицание, мы получили список файлов, имена которых содержат последовательность zip, которой предшествует любой символ, кроме b
или g
. Обратите внимание, что файл zip
не был найден. Символ отрицания не отменяет необходимости присутствия символа в заданной позиции, он лишь требует, чтобы символ в этой позиции не принадлежал указанному множеству.
Символ крышки обозначает операцию отрицания, только если является первым символом в выражении в квадратных скобках; в противном случае он теряет свое специальное значение и превращается в обычный символ.
Традиционные диапазоны символов
Если необходимо сконструировать регулярное выражение, которое находило бы в наших списках все файлы с именами, начинающимися с заглавной буквы, это можно выполнить следующим образом:
grep -h '^[ABCDEFGHIJKLMNOPQRSTUVWXZY]' dirlist*.txt GET HEAD JSONStream ...
Достаточно просто поместить 26 букв в верхнем регистре в выражение в квадратных скобках. Но необходимость ввода всех этих символов, вызывает сильнейшее уныние, поэтому предусмотрен другой способ:
grep -h '^[A-Z]' dirlist*.txt
Было сокращено множество с 26 буквами до 3-символьного диапазона. Так можно выразить любой диапазон символов и даже несколько диапазонов, например, для поиска имен файлов, начинающихся с буквы или цифры:
grep -h '^[A-Za-z0-9]' dirlist*.txt
Как следует из примеров, символ «дефис» получает в диапазонах специальное значение, поэтому возникает вопрос: как включить дефис в выражение в квадратных скобках, чтобы он интерпретировался как обычный символ? Для этого достаточно поставить его в начало выражения. Например:
grep -h '^[-AZ]' dirlist*.txt
Следующее выражение найдет все имена файлов, содержащие дефис, букву A или букву Z.
Классы символов POSIX
Класс символа | Описание |
---|---|
[:alnum:] | Алфавитно-цифровые символы; эквивалент диапазона [A-Za-z0-9] в ASCII |
[:word:] | То же, что и [:alnum:], с дополнительным символом подчеркивания (_) |
[:alpha:] | Алфавитные символы; эквивалент диапазона [A-Za-z] в ASCII |
[:blank:] | Включает символы пробела и табуляции |
[:cntrl:] | Управляющие символы ASCII; включает символы ASCII с кодами от 0 до 31 и 127 |
[:digit:] | Цифры от 0 до 9 |
[:graph:] | Отображаемые символы; включает символы ASCII с кодами от 33 до 126 |
[:lower:] | Символы нижнего регистра |
[:punct:] | Знаки пунктуации; эквивалент класса [-!"#$%&'()*+,./:;<=>?@[\\\]_`{|}~] в ASCII |
[:print:] | Печатаемые символы; Все символы из класса [:graph:] и пробел |
[:space:] | Пробельные символы, включая пробел, табуляцию, возврат каретки, перевод строки, вертикальную табуляцию и перевод формата; эквивалент класса [ \t\r\n\v\f] в ASCII |
[:upper:] | Символы верхнего регистра |
[:xdigit:] | Символы, используемые для представления шестнадцатеричных цифр; эквивалент класса [0-9A-Fa-f] в ASCII |
Пример использования класса символов:
ls -l /etc/[[:upper:]]*
Простые и расширенные регулярные выражения POSIX
Как раз когда, казалось бы, проблема путаницы с диалектами регулярных выражений решена, обнаруживается, что стандарт POSIX также делит реализации регулярных выражений на два вида: простые регулярные выражения (Basic Regular Expressions, BRE) и расширенные регулярные выражения (Extended Regular Expressions, ERE). Особенности, рассматривавшиеся до сих пор, поддерживаются всеми POSIX-совместимыми приложениями и приложениями, реализующими
BRE. Программа grep — одна из них.
Чем различаются BRE и ERE? Различия касаются наборов метасимволов. В диалекте BRE распознаются следующие метасимволы:
^ $ . [ ] *
Все остальные считаются литералами. В ERE во множество метасимволов (с соответствующими им функциями) добавляются
( ) { } ? + |
Однако (что самое интересное) символы ( ) { }
интерпретируются в BRE как метасимволы, если они экранированы символом «обратный слеш», тогда как в ERE присутствие обратного слеша перед этими же метасимволами превращает их в литералы.
Поскольку здесь рассматриваем особенности, являющиеся частью ERE, необходимо использовать другую версию grep. Традиционно диалект ERE поддерживался программой egrep, но GNU-версия grep также поддерживает расширенные регулярные выражения при вызове ее с параметром -E
.
Чередование
Первой особенностью расширенных регулярных выражений, которую здесь обсудим, будет чередование (alternation, или выражение выбора) — оно позволяет выбирать совпадение с одним из нескольких выражений. Так же как выражения
в квадратных скобках позволяют одному символу соответствовать множеству указанных символов, чередование позволяет находить совпадение с множеством строк или других регулярных выражений.
Для демонстрации воспользуемся комбинацией команд grep и echo. Сначала попробуем выполнить простое сопоставление строк:
echo AAA | grep AA AAA echo BBB | grep AAA
Достаточно простой пример, в котором передаем по конвейеру вывод команды echo на ввод grep и видим результат. Если обнаруживается совпадение, видим вывод; если совпадение отсутствует, ничего не выводится.
Теперь добавим чередование, обозначаемое метасимволом вертикальной черты:
echo BBB | grep -E 'AAA|BBB' BBB
Здесь видим регулярное выражение 'AAA|BBB', которое означает «совпадение со строкой AAA или со строкой BBB». Так как это расширенная особенность, добавляем в команду grep параметр -E
(вместо этого можно было бы использовать программу egrep) и заключили регулярное выражение в кавычки, чтобы предотвратить интерпретацию командной оболочкой символа вертикальной черты как оператора конвейера.
В чередовании может быть более двух вариантов:
echo AAA | grep -E 'AAA|BBB|CCC' AAA
Для объединения с другими элементами регулярного выражения чередование можно заключать в круглые скобки ():
grep -Eh '^(bz|gz|zip)' dirlist*.txt
Этому выражению будут соответствовать имена файлов из наших списков, начинающиеся с bz
, gz
или ip
. Если отбросить круглые скобки, смысл регулярного выражения изменится, и ему будут соответствовать имена, начинающиеся с bz
или содержащие gz
или zip
:
grep -Eh '^bz|gz|zip' dirlist*.txt
Квантификаторы
Расширенные регулярные выражения поддерживают несколько способов определения числа совпадений с элементом.
? – совпадение с элементом ноль или один раз
Этот квантификатор фактически означает: «совпадение с предыдущим элементом необязательно». Представьте, что нужно проверить допустимость номера телефона, и предполагается, что номер допустим, если представлен в одной из двух форм:
- (nnn) nnn-nnnn;
- nnn nnn-nnnn.
Для проверки можно было бы использовать следующее регулярное выражение:
^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$
В этом выражении за круглыми скобками следуют знаки вопроса, указывающие, что скобки могут либо отсутствовать, либо присутствовать один раз. И снова, поскольку круглые скобки считаются метасимволами (в ERE), мы экранировали их
обратными слешами, чтобы они интерпретировались как литералы.
Попробуем применить это выражение:
echo "(777) 123-4567" | grep -E '^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$' (777) 123-4567 echo "777 123-4567" | grep -E '^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$' 777 123-4567 echo "(77A) 123-4567" | grep -E '^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$'
Здесь регулярному выражению соответствуют обе формы записи номера телефона, но ему не соответствует номер, содержащий нецифровые символы.
* – совпадение с элементом ноль или более раз
Подобно метасимволу ?
, звездочка *
обозначает необязательный элемент; однако, в отличие от знака вопроса ?
, этот элемент может встречаться любое число раз, а не только единожды. Представьте, что нам нужно проверить, является ли строка предложением. Чтобы удовлетворять нашим требованиям, строка должна начинаться с большой буквы, содержать любое число букв верхнего и нижнего регистра и пробелов и заканчиваться точкой. Для поиска совпадений с этим (очень приблизительным) определением предложения воспользуемся следующим регулярным выражением:
[[:upper:]][[:upper:][:lower:] ]*\. # более практичный вариант со знаками пунктуации grep -E '[[:upper:]][[:upper:][:lower:][:punct:] ]*\.' sentence.txt
Выражение состоит из трех элементов: выражение в квадратных скобках с классом символов [:upper:]
, выражение в квадратных скобках с двумя классами символов, [:upper:]
и [:lower:]
, и пробелом, и точка, экранированная обратным слешем. Второй элемент сопровождается метасимволом *
, поэтому в нашем предложении ему может соответствовать любое число букв верхнего и нижнего регистра и пробелов, следующих за первой буквой верхнего регистра:
echo "This work." | grep -E '[[:upper:]][[:upper:][:lower:] ]*\.' This work. echo "This Work." | grep -E '[[:upper:]][[:upper:][:lower:] ]*\.' This Work. echo "dont work" | grep -E '[[:upper:]][[:upper:][:lower:] ]*\.'
Первые два примера соответствуют выражению, а третий — нет, потому что в нем отсутствует обязательный первый символ верхнего регистра и завершающая точка.
+ – совпадение с элементом один или более раз
Метасимвол +
действует почти так же, как *
, но требует совпадения с предыдущим элементом не менее одного раза. Следующему регулярному выражению будут соответствовать только строки, состоящие из групп, насчитывающих один или несколько алфавитных символов и разделенных одиночными пробелами:
^([[:alpha:]]+ ?)+$
Опробуем его:
echo "This that" | grep -E '^([[:alpha:]]+ ?)+$' This that echo "a b c" | grep -E '^([[:alpha:]]+ ?)+$' a b c echo "a b 8" | grep -E '^([[:alpha:]]+ ?)+$' echo "a b c" | grep -E '^([[:alpha:]]+ ?)+$'
Как видите, этому выражению не соответствует строка «a b 8», потому что она содержит неалфавитный символ; точно так же ему не соответствует строка «a b c», потому что между символами b и c в ней присутствует больше одного пробела.
{ } — совпадение с элементом определенное число раз
Метасимволы {
и }
используются, чтобы выразить минимальное и максимальное число обязательных совпадений. Эти числа можно представить четырьмя возможными способами, как показано ниже:
Спецификатор | Значение |
---|---|
{n} | Предыдущий элемент соответствует, если встречается точно n раз |
{n,m} | Предыдущий элемент соответствует, если встречается не менее n и не более m раз |
{n,} | Предыдущий элемент соответствует, если встречается n или более раз |
{,m} | Предыдущий элемент соответствует, если встречается не более m раз |
Вернемся к примеру с телефонными номерами и воспользуемся этим методом определения повторений, чтобы упростить исходное регулярное выражение:
^\(?[0-9][0-9][0-9]\)? [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]$
до
^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$
Пробуем:
echo "(444)333-6789" | grep -E '^\(?[0-9]{3}\)?[0-9]{3}-[0-9]{4}$' (444)333-6789 echo "444 333-6789" | grep -E '^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$' 444 333-6789 echo "7444 333-6789" | grep -E '^\(?[0-9]{3}\)? [0-9]{3}-[0-9]{4}$'
Как видите, измененная версия регулярного выражения успешно справляется с проверкой номеров, с круглыми скобками и без них, и отвергает неправильно оформленные номера.
еще пример:
echo "+7(444)333-6789" | grep -E '^\+?[0-9]{1}\(?[0-9]{3}\)?[0-9]{3}-[0-9]{4}$' +7(444)333-6789 echo "8(444)333-6789" | grep -E '^\+?[0-9]{1}\(?[0-9]{3}\)?[0-9]{3}-[0-9]{4}$' 8(444)333-6789
Практические примеры применения регулярных выражений
Проверка списка телефонов с помощью grep
В предыдущем примере мы брали телефонные номера по одному и проверяли правильность их оформления. На практике же часто приходится проверять списки телефонов, поэтому давайте создадим такой список. Для этого воспользуемся волшебной магией командной строки:
for i in {1..10}; do echo "(${RANDOM:0:3}) ${RANDOM:0:3}-${RANDOM:0:4}" >> phonelist.txt; done
Однако если заглянуть в файл, можно заметить проблему:
cat phonelist.txt (312) 148-65 (215) 291-2637 (817) 159-1790 (668) 275-6987 (312) 184-8927 (170) 233-1613 (148) 128-1514 (128) 150-1831 (248) 234-1914 (250) 286-872
Некоторые номера оформлены неправильно, что очень хорошо для целей демонстрации их проверки с помощью grep
Было бы полезно просканировать файл в поисках недопустимых номеров и вывести их:
grep -Ev '^\([0-9]{3}\) [0-9]{3}-[0-9]{4}$' phonelist.txt (312) 148-65 (250) 286-872
Здесь мы использовали параметр -v
, чтобы обратить сопоставление и вывести только строки, не соответствующие указанному выражению. Само выражение содержит якорные метасимволы на обоих концах и тем самым гарантирует отсутствие дополнительных символов слева и справа от номера. Кроме того, в отличие от примера, приведенного выше, это выражение также требует обязательного наличия круглых скобок в номере.
Old stuff
Как найти все файлы, содержащие искомый текст
grep -rnw '/path/to/dir' -e "tamplate"
- -r – рекурсивный поиск.
- -n – вывод номера строки.
- -w – только целые слова.
- -l – вывод имени файла, где было совпадение.
Эффективности добавят следующие флаги:
–exclude - Шаблон для исключения файлов, например: поиск везде, кроме файлов с расширением .py:
grep --exclude=*.py -rnw '/path/to/dir' -e "tamplate"
–include - Поиск только в определённых файлах, например: только в файлах с расширениями .h и .c:
grep --include=\*.{c,h} -rnw '/path/to/dir' -e "tamplate"
–exclude-dir и –include-dir - то же, только для выборки директорий, например: исключить директории один, two и любые, начинающиеся на а:
grep --exclude-dir={один,two,а*} -rnw '/path/to/dir' -e "tamplate"