GREP (акроним от «global regular expression print») — одна из самых популярных утилит в операционных системах Linux.
С помощью нее можно не только выполнять поиск фраз (последовательности символов) во множестве файлов одновременно на основе регулярных выражений, но и фильтровать консольный вывод других команд, оставляя только необходимую информацию.
В этом руководстве будет рассмотрен поиск заданных выражений в наборе текстовых файлов различного содержания с помощью утилиты GREP.
Все показанные примеры запускались на облачном сервере Timeweb Cloud под управлением операционной системы Ubuntu версии 22.04.
cloud
Схема команды GREP имеет следующий вид:
grep [ОПЦИИ] [ВЫРАЖЕНИЕ] [ИСТОЧНИКИ]
В этом случае:
Если же команда GREP используется для преобразования вывода другой команды, то ее схема будет выглядеть немного иначе:
[КОМАНДА] | grep [ОПЦИИ] [ВЫРАЖЕНИЕ]
Таким образом:
КОМАНДА. Произвольная команда с собственным набором параметров, вывод которой необходимо отфильтровать.
При этом символ «|» необходим для образования конвейера (pipeline) выполнения команд, который перенаправляет потоки так, что вывод произвольной команды становится вводом команды GREP.
Понять нюансы работы утилиты GREP лучше всего на небольших примерах по поиску вхождений заданных фраз. Поэтому сперва мы создадим несколько текстовых файлов, а после протестируем на них команду GREP.
Для начала подготовим отдельный каталог, в котором будет выполняться поиск:
mkdir /texts
После чего создадим первый файл:
nano texts/blok
Содержимым выступит одно из стихотворений Александра Блока:
Ночь, улица, фонарь, аптека,
Бессмысленный и тусклый свет.
Живи еще хоть четверть века —
Всё будет так. Исхода нет.
Умрёшь — начнёшь опять сначала
И повторится все, как встарь:
Ночь, ледяная рябь канала,
Аптека, улица, фонарь.
Теперь создадим второй файл:
nano texts/code.py
Его содержимым станет простой скрипт на языке Python:
from datetime import date
dateNow = date.today()
print("Текущее время:", dateNow)
Наконец, создадим третий файл:
nano texts/page.html
На этот раз внутри окажется тривиальная HTML-разметка:
<html>
<head>
<title>Некий заголовок</title>
</head>
<body>
<div class="block">
<p>Здесь есть золото</p>
</div>
<div class="block">
<p>Смесь воска и облаков</p>
</div>
<div class="block block_special">
<p>Днесь нет ничего</p>
</div>
</body>
</html>
Таким образом с помощью файлов разного формата можно более наглядно разобраться, что делает команда GREP, задействовав всю вариативность функций утилиты.
Регулярные выражения — основа команды GREP. В отличие от обычной строки, регулярные выражения содержат специальные символы, позволяющие задавать фразы с определенной степенью изменчивости.
При использовании утилиты GREP регулярные выражения помещаются в одинарные кавычки:
'^date[[:alpha:]]*'
Таким образом, полная команда может выглядеть так:
grep '^date[[:alpha:]]*' texts/*
Консольный вывод в этом случае будет таким:
texts/code.py:dateNow = date.today()
Однако использование двойных кавычек позволяет передавать в выражение различные системные данные. Например, можно сперва создать переменную окружения с искомым выражением:
PATTERN="^date[[:alpha:]]*"
А потом использовать ее в команде GREP:
grep "$PATTERN" ./texts/*
В дополнение к этому, задействование одинарных косых кавычек (клавиша «Ё» в английской раскладке) позволяет использовать команды подпроцессора bash внутри команды GREP.
Например, можно извлечь регулярное выражение из заранее подготовленного файла:
grep `cat somefile` ./texts/*
Обратите внимание, что с помощью символа звездочки (маска) можно указать сразу все файлы, находящиеся в каталоге. Тем не менее, команда GREP не запрещает указание только одного файла:
grep '^date[[:alpha:]]*' texts/code.py
По той причине, что регулярные выражения являются универсальным языком, используемым во многих операционных системах и языках программирования, их изучение — отдельная обширная тема.
Тем не менее, имеет смысл коротко пройтись по основным специальным символам и их функциям.
При этом важно помнить, что регулярные выражения в Linux могут работать в двух режимах — базовом (Basic Regular Expression, BRE) и расширенном (Extended Regular Expression, ERE).
Расширенный режим активируется с помощью дополнительного флага -E
. Разница между обоими режимами в количестве доступных спецсимволов и соответственно широте доступного функционала.
Базовый синтаксис позволяет задавать лишь общие формальные конструкции, не учитывающие специфику конфигурации их символов.
Символ циркумфлекса указывает, что искомая последовательность символов должна находиться в начале строки:
grep '^Ночь' texts/*
Консольный вывод будет следующим:
texts/block:Ночь, улица, фонарь, аптека,
texts/block:Ночь, ледяная рябь канала,
Знак доллара указывает, что искомая последовательность символов должна находиться в конце строки:
grep '</p>$' texts/*
В консольном терминале появится вот такой вывод:
texts/page.html: <p>Здесь есть золото</p>
texts/page.html: <p>Смесь воска и облаков</p>
texts/page.html: <p>Днесь нет ничего</p>
Обратите внимание, что консольный вывод сохраняет оригинальную запись найденных строк так, как они представлены внутри файлов.
Символы обратной косой черты и знака меньше указывают, что искомая фраза должна находиться в начале слова:
grep '\<фо' texts/*
Итогом выполнения этой команды станет следующий консольный вывод:
texts/block:Ночь, улица, фонарь, аптека,
texts/block:Аптека, улица, фонарь.
Символы обратной косой черты и знака больше указывают, что искомая последовательность символов должна находиться в конце слова:
grep 'рь\>' texts/*
Консольный вывод после выполнения команды будет таким:
texts/block:Ночь, улица, фонарь, аптека,
texts/block:И повторится все, как встарь:
texts/block:Аптека, улица, фонарь.
Указать начало или конец слова можно с помощью другой более универсальной последовательности символов — обратная косой черты и буквы b.
Например, так указывается начало:
grep '\bнет' texts/*
Консольный вывод будет таким:
texts/block:Всё будет так. Исхода нет.
texts/page.html: <p>Днесь нет ничего</p>
А так конец:
grep '<div\b' texts/*
В этом случае вывод в консольном терминале будет следующим:
texts/page.html: <div class="block">
texts/page.html: <div class="block">
texts/page.html: <div class="block block_special">
Либо можно указать и то, и другое:
grep '\bи\b' texts/*
В консольном терминале появится нижеследующий вывод:
texts/block:Бессмысленный и тусклый свет.
texts/page.html: <p>Смесь воска и облаков</p>
Некоторые символы в искомых фразах можно оставить без точного указания с помощью символа точки:
grep '..есь' texts/*
Вывод в терминале консоли окажется таким:
texts/page.html: <p>Здесь есть золото</p>
texts/page.html: <p>Смесь воска и облаков</p>
texts/page.html: <p>Днесь нет ничего</p>
Расширенный синтаксис, в отличие от базового, позволяет задавать точное количество символов в искомых фразах, тем самым расширяя спектр возможных совпадений.
Чтобы не выполнять команду GREP несколько раз подряд, можно указать сразу несколько шаблонов в одном регулярном выражении:
grep -E '^Ночь|</p>$' texts/*
Результатом работы этой команды будет комбинированный консольный вывод, содержащий результат поиска двух отдельных регулярных выражений, показанных ранее:
texts/block:Ночь, улица, фонарь, аптека,
texts/block:Ночь, ледяная рябь канала,
texts/page.html: <p>Здесь есть золото</p>
texts/page.html: <p>Смесь воска и облаков</p>
texts/page.html: <p>Днесь нет ничего</p>
В некоторых случаях определенные символы искомой фразы могут количественно отличаться. Поэтому в регулярном выражении можно указать диапазон допустимого количества определенных символов.
grep -E 'ес{1,2}' texts/*
Консольный вывод этого вызова будет таким:
texts/block:Бессмысленный и тусклый свет.
texts/page.html: <p>Здесь есть золото</p>
texts/page.html: <p>Смесь воска и облаков</p>
texts/page.html: <p>Днесь нет ничего</p>
Однако часто используемые интервалы повторений удобнее записывать в виде специальных символов, тем самым упрощая внешний вид регулярного выражения.
Интервал повторений от одного до бесконечности можно выразить в виде знака сложения:
grep -E 'ес+' texts/*
В этом случае консольный вывод не будет отличаться от предыдущего примера:
texts/block:Бессмысленный и тусклый свет.
texts/page.html: <p>Здесь есть золото</p>
texts/page.html: <p>Смесь воска и облаков</p>
texts/page.html: <p>Днесь нет ничего</p>
Интервал повторений от 0 до 1 можно выразить в виде знака вопроса:
grep -E 'ss?' texts/*
В результате выполнения этой команды в консольном терминале появится следующий вывод:
texts/page.html: <div class="block">
texts/page.html: <div class="block">
texts/page.html: <div class="block block_special">
Вместо одного конкретного символа можно указать целый набор, обрамленный квадратными скобками:
grep -E '[Вв]с[её]' texts/*
Консольный вывод в этом случае будет таким:
texts/block:Всё будет так. Исхода нет.
texts/block:И повторится все, как встарь:
Слишком большой набор допустимых символов можно заменить на диапазон, записанный через тире:
grep -E 'h[a-z]+' texts/*
Результатом выполнения этой команды будет следующий вывод:
texts/page.html:<html>
texts/page.html: <head>
texts/page.html: </head>
texts/page.html:</html>
Более того, наборы и диапазоны символов можно объединять:
grep -E 'h[abcd-z]+' texts/*
Каждый диапазон неявно преобразуется в набор символов:
[a-e]
в [abcde]
[0-6]
в [0123456]
[a-eA-F]
в [abcdeABCDEF]
[A-Fa-e]
в [ABCDEFabcde]
[A-Fa-e0-9]
в [ABCDEFabcde0123456789]
[a-dA-CE-G]
в [abcdABCEFG]
[acegi-l5-9]
в [acegijkl56789]
Часто используемые диапазоны можно заменить заранее предопределенным типом символов, название которого указывается в квадратных скобках с двоеточием:
|
символы от a до z в нижнем регистре |
|
символы от A до Z в верхнем регистре |
|
символы всех букв |
|
символы всех цифр |
|
символы всех букв и цифр |
При этом важно понимать, что тип символов — отдельная синтаксическая конструкция. Это значит, что она сама по себе должна быть обрамлена квадратными скобками, обозначающими набор или диапазон символов:
grep -E '[[:alpha:]]+есь' texts/*
Консольный вывод этой команды будет как показано ниже:
texts/page.html: <p>Здесь есть золото</p>
texts/page.html: <p>Смесь воска и облаков</p>
texts/page.html: <p>Днесь нет ничего</p>
Чтобы отфильтровать вывод другой команды, после нее необходимо написать символ прямой черты, за которым следует стандартный вызов утилиты GREP, но без указания файлов для поиска:
cat texts/code.py | grep 'import'
Консольный вывод, по аналогии с поиском в обычных файлах, будет содержать строки с найденными вхождениями указанных фраз:
from datetime import date
В данном случае команда cat
извлекает содержимое файла и передает его в поток ввода утилиты GREP.
Помимо регулярных выражений можно указать дополнительные ключи команды GREP — специальные опции в формате флагов, уточняющие поиск.
Активирует режим расширенных регулярных выражений, разрешающий использовать большее количество специальных символов.
Выполняет поиск регулярного выражения, не учитывая регистр символов:
grep -E -i 'вс[её]' texts/*
Вот такой консольный вывод соответствует этой команде:
texts/block:Всё будет так. Исхода нет.
texts/block:И повторится все, как встарь:
При этом флаги можно указывать слитно:
grep -Ei 'вс[её]' texts/*
Выполняет поиск таким образом, чтобы указанное регулярное выражение являлось полноценным словом (а не его подстрокой) в найденной строке:
grep -w и texts/*
Обратите внимание, что при указании обычной строки, не содержащий специальные символы, кавычки можно не указывать.
Результатом выполнения этой команды станет вот такой консольный вывод:
texts/block:Бессмысленный и тусклый свет.
texts/page.html: <p>Смесь воска и облаков</p>
По умолчанию команда GREP выводит в консольный терминал строки с найденными фразами. Однако результат можно инвертировать, выводя в консольный терминал строки без найденных фраз:
grep -v 'today' script.py
Консольный вывод будет содержать все строки кроме той, в которой было найдено вхождение:
from datetime import date
print("Текущее время:", dateNow)
Чтобы не вызывать команду несколько раз, можно сразу указать несколько выражений:
grep -e 'Ночь' -e 'html' texts/*
Результат работы этой команды будет идентичен такой записи:
grep -E 'Ночь|html' texts/*
В обоих случаях в консольном терминале появится следующий вывод:
texts/block:Ночь, улица, фонарь, аптека,
texts/block:Ночь, ледяная рябь канала,
texts/page.html:<html>
texts/page.html:</html>
Выполняет рекурсивный поиск в указанной директории до максимальной глубины вложенности:
grep -r '[Ff]ilesystem' /root
В консольном терминале появится вывод, содержащий пути файлов, находящихся на разных уровнях вложенности относительно указанной директории:
/root/parser/parser/settings.py:#HTTPCACHE_STORAGE = "scrapy.extensions.httpcache.FilesystemCacheStorage"
/root/resize.log:Resizing the filesystem on /dev/vda1 to 3931904 (4k) blocks.
/root/resize.log:The filesystem on /dev/vda1 is now 3931904 (4k) blocks long.
Разрешает использование специальных символов в качестве символов искомой фразы:
grep -F '[' texts/*
Без указания этого флага в консольном терминале возникнет ошибка:
grep: Invalid regular expression
Альтернативой этому флагу будет использование символа экранирования в виде обратной косой черты (\
):
grep '\[' texts/*
Позволяет ограничить поиск только указанными файлами:
grep --include='*.py' 'date' texts/*
Консольный вывод будет таким:
texts/code.py:from datetime import date
texts/code.py:dateNow = date.today()
texts/code.py:print("Текущее время:", dateNow)
Эту команду можно также записать без маски, используя дополнительный флаг рекурсивного поиска.:
grep -r --include='*.py' 'date' texts
Избирательно исключает определенные файлы из списка источников поиска:
grep --exclude='*.py' 'и' texts/*
Консольный вывод окажется следующим:
texts/block:Ночь, улица, фонарь, аптека,
texts/block:Бессмысленный и тусклый свет.
texts/block:Живи еще хоть четверть века —
texts/block:И повторится все, как встарь:
texts/block:Аптека, улица, фонарь.
texts/page.html: <title>Некий заголовок</title>
texts/page.html: <p>Смесь воска и облаков</p>
texts/page.html: <p>Днесь нет ничего</p>
Некоторые параметры команды GREP влияют только на вывод результатов поиска, улучшая его информативность и наглядность.
Для повышения информативности результата работы утилиты GREP можно добавить номера строк, в которых были найдены искомые фразы:
grep -n '</p>$' texts/*
Каждая строка в выводе будет дополнена номером строки
texts/page.html:8: <p>Здесь есть золото</p>
texts/page.html:12: <p>Смесь воска и облаков</p>
texts/page.html:16: <p>Днесь нет ничего</p>
Выводит заданное количество строк, идущих перед строками с найденными вхождениями:
grep -B3 'Смесь' texts/*
После флага указывается число предыдущих строк, которые требуется вывести в консольный терминал:
texts/page.html- </div>
texts/page.html-
texts/page.html- <div class="block">
texts/page.html: <p>Смесь воска и облаков</p>
Выводит заданное количество строк, идущих после строк с найденными вхождениями:
grep -A3 'Смесь' texts/*
После флака указывается число последующих строк, которые требуется вывести в консольный терминал:
texts/page.html: <p>Смесь воска и облаков</p>
texts/page.html- </div>
texts/page.html-
texts/page.html- <div class="block block_special">
Выводит заданное количество строк, идущих как перед, так и после строк с найденными вхождениями:
grep -C3 'Смесь' texts/*
После флака указывается число предыдущих и последующих строк, которые требуется вывести в консольный терминал:
texts/page.html- </div>
texts/page.html-
texts/page.html- <div class="block">
texts/page.html: <p>Смесь воска и облаков</p>
texts/page.html- </div>
texts/page.html-
texts/page.html- <div class="block block_special">
Вместо списка найденных строк команды GREP выводит только их количество:
grep -c 'и' texts/*
Консольный вывод будет содержать количество найденных совпадений во всех указанных файлах:
texts/block:5
texts/code.py:0
texts/page.html:3
Если же в качестве источника указывается лишь один файл:
grep -c 'и' texts/block
То консольный вывод будет содержать только число:
5
Флаг позволяет вывести только названия файлов, в которых найдены совпадения:
grep -l 'и' texts/*
В этом случае консольный вывод будет таким:
texts/block
texts/page.html
Сокращает количество выводящихся в консольный терминал строк до указанного рядом с флагом:
grep -m2 'и' texts/*
Консольный вывод будет таким:
texts/block:Ночь, улица, фонарь, аптека,
texts/block:Бессмысленный и тусклый свет.
texts/page.html: <title>Некий заголовок</title>
texts/page.html: <p>Смесь воска и облаков</p>
Как видно, ограничивающее число влияет не на весь вывод в целом, а на строки каждого файла.
Ищет точное совпадение целой строки без какой-либо вариативности:
grep -x 'Всё будет так. Исхода нет.' texts/*
Консольный вывод следующий:
texts/block:Всё будет так. Исхода нет.
Подготовили для вас выгодные тарифы на облачные серверы
Команда GREP в Linux — наиболее гибкий и точный инструмент для поиска выражений в больших объемах текстовой информации.
Во время использования команды необходимо указать следующие элементы:
Помимо этого утилита используется для фильтрации вывода других команд с помощью перенаправления потоков ввода и вывода.
При этом центральной частью команды GREP являются регулярные выражения. В отличие от обычной строки, они позволяют задать фразу с определенной степенью изменчивости так, чтобы она соответствовала нескольким похожим вхождениям.
Существует два режима работы регулярных выражений:
Расширенный режим предоставляет полную гибкость и точность при работе с регулярными выражениями.
Лишь в редких случаях, когда требуется найти только вхождения тривиальных паттернов, можно ограничиться базовым режимом.