grep

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.

Класс символа Описание
[: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 также делит реализации регулярных выражений на два вида: простые регулярные выражения (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

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

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, чтобы обратить сопоставление и вывести только строки, не соответствующие указанному выражению. Само выражение содержит якорные метасимволы на обоих концах и тем самым гарантирует отсутствие дополнительных символов слева и справа от номера. Кроме того, в отличие от примера, приведенного выше, это выражение также требует обязательного наличия круглых скобок в номере.

Как найти все файлы, содержащие искомый текст

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"
  • grep.txt
  • Последнее изменение: 2021/06/20 11:50
  • admin