Вступление
Grep - мощный, но очень простой инструмент. По умолчанию он просматривает входные данные и печатает одну или несколько строк, содержащих текст, соответствующий шаблону, указанному в вызове команды.
Прежде чем grep
стал таким широко распространенным инструментом для
системы GNU / Linux, он был частной утилитой, написанной Кеном
Томпсоном для поиска в
файлах. Интересная часть истории заключается в том, что к нему подошел
его менеджер и попросил инструмент, который бы делал именно это.
Он ответил, что что-нибудь придумает в одночасье, хотя на самом деле он использовал это время для улучшения кода и исправления некоторых ошибок. Когда он представил инструмент на следующий день, действительно казалось, что он был написан в кратчайшие сроки.
В этой статье мы изучим основы grep
и его использование, просмотрев
его параметры и несколько примеров.
Использование и варианты Grep
Общая форма команды grep
:
$ grep [OPTION...] [PATTERNS] [FILE...]
Мы можем указать ноль или несколько аргументов OPTION , один или
несколько ШАБЛОНОВ и ноль или несколько аргументов FILE. Если
аргумент FILE не указан, grep
просматривает рабочий каталог ( .
),
Если задана опция рекурсии; в противном случае grep
ищет стандартный
входной канал.
Есть четыре основных варианта grep
. В зависимости от того, что лучше
всего соответствует нашим потребностям, мы выбираем тот, который будем
использовать, указав аргумент OPTION:
- -G или --basic-regexp - при использовании интерпретирует шаблон как базовое регулярное выражение (BRE). Этот вариант используется по умолчанию (если не указаны другие параметры).
- -E или --extended-regexp - интерпретирует шаблон как расширенное регулярное выражение (ERE).
- -F или --fixed-strings - интерпретирует шаблон как фиксированные строки, а не как регулярные выражения.
- -P или --perl-regexp - интерпретирует шаблон как Perl-совместимые регулярные выражения (PCRE). Этот вариант по-прежнему имеет некоторые нереализованные функции и может выдавать предупреждения. Его следует считать скорее экспериментальным при использовании с определенными параметрами и рекомендуется только для опытных пользователей.
Стоит отметить, что в настоящее время grep
- это семейство
инструментов, в которое входят egrep
, fgrep
и rgrep
. Они такие
же, как grep -E
, grep -F
и grep -R
соответственно, но устарели
как автономные инструменты и предоставляются только потому, что
некоторые программы по-прежнему полагаются на них.
В наших примерах мы будем использовать второй вариант, хотя примеры в следующих разделах должны применяться и к другим вариантам.
Поиск в файле
Скажем, у нас есть test.txt
со следующим содержимым:
hello
hElLo
This line does not include the word we're looking for.
helloHello
This is the paragraph that has multiple sentences. We'll put one more hello here.
Test line.
Another hello line.
Мы хотим найти все строки, содержащие слово hello
. Не имеет значения,
где находится слово в строке, и не имеет значения, является ли оно
частью более длинного слова, такого как helloHello
.
Мы будем использовать grep -E
с шаблоном hello
в файле test.txt
$ grep -E hello test.txt
Выполнение этой команды даст:
hello
helloHello
This is the paragraph that has multiple sentences. We'll put one more hello here.
Another hello line.
Примечание. Обычно узор помещают в кавычки, чтобы визуально отделить его как узор. Это также позволяет нам использовать в качестве поискового запроса несколько слов вместо одного.
Поиск нескольких слов
Иногда нам нужно найти пару слов вместо одного. Это делается путем простого включения условий поиска в кавычки:
$ grep -E "This is" test.txt
Выполнение этого приведет к:
This is the paragraph that has multiple sentences. We'll put one more hello here.
Имейте в виду, что grep
чувствителен к регистру. Если бы мы
"this is"
, ничего бы не вернулось.
Использование регулярных выражений
Теперь давайте воспользуемся регулярным выражением, чтобы выделить слово
hello
и пропустить такие результаты, как helloHello
:
$ grep -E "(\s|[^a-zA-Z0-9_]*)hello\s" test.txt
Выполнение этой команды приведет к:
hello
This is the paragraph that has multiple sentences. We'll put one more hello here.
Another hello line.
Указание правил сопоставления с помощью флагов
Есть несколько опций, которые помогают нам легче указать правила сопоставления:
- -e шаблон
Этот флаг означает, что исходящую строку следует интерпретировать как
шаблон. По умолчанию, если у вас есть один шаблон, нет необходимости
отмечать его с помощью -e
. Если у вас несколько шаблонов, вам нужно
будет отметить их все.
Например, мы можем искать несколько шаблонов, например:
$ grep -E -e "hello" -e "the" test.txt
Эта команда приведет к:
hello
This line does not include the word we're looking for.
helloHello
This is the paragraph that has multiple sentences. We'll put one more hello here.
Another hello line.
- -f файл
Этот флаг позволяет нам получать шаблоны из файла, по одному в каждой строке. Когда мы имеем дело с множеством шаблонов, их проще поместить в файл, чем хранить их все в командной строке. Учитывается каждый шаблон, присутствующий в файле:
$ grep -E -f patterns.txt test.txt
Вот так выглядит наш файл patterns.txt
^helloHello$
*line*
Выполнение команды приведет к:
This line does not include the word we're looking for.
helloHello
Test line.
Another hello line.
- -i или --ignore-case
Этот флаг отменяет поведение по умолчанию с учетом регистра и возвращает все совпадающие шаблоны, независимо от регистра:
$ grep -E -i "hello" test.txt
Выполнение этой команды приведет к:
hello
hElLo
helloHello
This is the paragraph that has multiple sentences. We'll put one more hello here.
Another hello line.
На этот раз, даже несмотря на то, что наш поисковый helLo
"hello"
,
были возвращены другие соответствующие строки, такие как helLo.
Примечание: -y
- устаревшая версия -i
которая делает то же
самое, но сохраняется только для обратной совместимости.
- -v или --invert-match
Инвертирует совпадение - возвращает строки, не соответствующие нашему шаблону:
$ grep -E -v "hello" test.txt
Выполнение этой команды приведет к:
hElLo
This line does not include the word we're looking for.
Test line.
- -w или --word-regexp
Ищет "отдельные" слова - до и после которых вы встретите пробел, новую строку, табуляцию и т. Д. Если они находятся в начале или конце строки, предшествующие и последующие составляющие, не являющиеся словами, такие как числа, символы или подчеркивания не имеют значения. Это сокращенный удобный флаг, позволяющий избежать написания регулярного выражения раньше:
$ grep -E -w "hello" test.txt
Результат будет таким же, как если бы мы просто ввели регулярное выражение для отдельных слов:
hello
This is the paragraph that has multiple sentences. We'll put one more hello here.
Another hello line.
- -x или --line-regexp
Ищет строки, соответствующие всему шаблону. То есть, если шаблон и вся строка совпадают, возвращается строка:
$ grep -E -x "helloHello" test.txt
Это удобный флаг, который позволяет нам пропустить запись регулярного выражения:
$ grep -E "^helloHello$" test.txt
У них будет тот же результат - в нашем случае одна строка, содержащая именно то, что мы указали:
helloHello
Контроль вывода
Иногда полученная информация может быть загроможденной. Если мы работаем
с большими файлами или не хотим полностью визуально видеть все
результаты, нам нужно контролировать вывод и изменять поведение. К
счастью, grep
поддерживает несколько флагов и параметров для этого:
- -c или --count
Подавляет вывод и возвращает количество совпавших строк:
$ grep -E -c "hello" test.txt
Эта команда приведет к единственному числу:
4
- -l
Подавляет основной вывод и отображает только имена файлов с совпадающими строками. Для этого примера мы добавим еще 4 файла в наш каталог. Два из них совпадают, а два других не соответствуют поисковому запросу:
$ grep -E -l "hello" *.txt
Последняя строка ( *.txt
) в нашей команде указывает программе искать
все файлы в текущем каталоге с расширением .txt
Мы также можем
перечислить их по очереди, но это более изящный способ.
Эта команда приведет к следующему:
01_contains_hello.txt
02_contains_hello.txt
test.txt
Напротив, опция -L показывает имена файлов, которые не имеют соответствий.
- -m число
Остановка поиска после num
найденных строк. При поиске в нескольких
файлах это число не переносится между файлами, и счет начинается заново
для каждого из них:
$ grep -E -m 2 "hello" test.txt
Эта команда приведет к следующему:
hello
helloHello
- -час
Подавляет имена файлов, в которых были найдены соответствия, учитывая, что мы ищем несколько файлов. Давайте сначала будем искать регулярно:
$ grep -E -m 1 "hello" *.txt
Это приводит к:
01_contains_hello.txt:hello
02_contains_hello.txt:hello
test.txt:hello
Однако, когда мы добавляем флаг -h
$ grep -E -h -m 1 "hello" *.txt
Результат:
hello
hello
hello
- -n
Показывает номер строки для каждой совпавшей строки:
$ grep -E -n "hello" test.txt
Это приводит к:
1:hello
4:helloHello
5: This is the paragraph that has multiple sentences. We'll put one more hello here.
7:Another hello line.
Практическое использование
Давайте рассмотрим несколько примеров практического использования
команды grep
, имея в виду предыдущие разделы.
Поиск по расширению файла с помощью Grep
Grep может выполнять поиск по любому заданному входу. Этот ввод может быть стандартным каналом ввода, указанным файлом или даже выводом другой ранее выполненной программы.
Несмотря на то, что существует много способов перечислить файлы по
расширению, grep
может упростить нам задачу.
Здесь мы будем использовать grep
для фильтрации вывода другого
инструмента, который рекурсивно перечисляет все файлы в текущем рабочем
каталоге. Мы попытаемся найти все файлы с .txt
и отсортировать их по
алфавиту, не обращая внимания на регистр:
$ find . | sort -f | grep -E "*.txt"
Обратите внимание, что в этой команде мы использовали |
, или так
называемая труба . Мы использовали его для перенаправления вывода
одной программы на ввод другой, вместо того, чтобы выводить его на
стандартный вывод, как мы обычно это делаем.
Предположим, что в нашем текущем рабочем каталоге есть следующие файлы:
./some_file1.txt
./05_contains_hello.txt
./subfolder
./subfolder/py_code.py
./subfolder/some_file.txt
./c_code.c
./markdown_file.md
./some_file2.txt
Результат нашей команды будет:
./05_contains_hello.txt
./some_file1.txt
./some_file2.txt
./subfolder/some_file.txt
Обработка содержимого файла с помощью Grep
Мы воспользуемся этим шансом, чтобы продемонстрировать еще одну grep
опцию grep. Предположим, у нас есть каталог проекта с огромным
количеством файлов с кодом в них и нет доступа к причудливой IDE.
Мы хотим реорганизовать некоторую функцию, поэтому нам будет очень полезно заранее найти все ее использования, чтобы не нарушить всю кодовую базу.
В нашем решении мы будем использовать -r
, grep
для рекурсивного
поиска всех файлов, содержащихся в текущем рабочем каталоге, и во всех
его подкаталогах:
$ grep -E -rn "osCreateMemoryBlock\([^)]*(\s*|\))"
Обратите внимание, что использование промежуточного регулярного выражения помогает нам быть более точными при поиске: мы даже учли случаи, когда аргументы функции записываются с новой строки.
Здесь мы объединили две опции - -r
и -n
в одну -rn
. Это потому,
что мы хотим знать, в какие строки необходимо внести изменения.
Вот как выглядит рабочий каталог для этой команды:
./subfolder2
./subfolder2/c_code.c
./subfolder2/memory_admin.c
./log_client.c
./signals.c
./log_server.c
./fifo_server.c
./fifo_client.c
./skelet.c
./subfolder1
./subfolder1/memory_handler.c
./subfolder1/random_code1.c
./subfolder1/c_code.c
./subfolder1/memory_creater.c
./shared_memory_writer.c
./shared_memory_reader.c
Выполнение команды выведет относительный путь к каждому файлу и номер
каждой строки, содержащей osCreateMemoryBlock
:
subfolder2/c_code.c:47:void *osCreateMemoryBlock(const char* filePath, unsigned size);
subfolder2/c_code.c:116:void *osCreateMemoryBlock(const char* filePath,
subfolder2/memory_admin.c:54: osMemoryBlock* pMsgBuf = osCreateMemoryBlock(argv[1], sizeof(osMemoryBlock));
subfolder2/memory_admin.c:116:void *osCreateMemoryBlock(const char* filePath,
log_server.c:116:void *osCreateMemoryBlock(const char* filePath,
subfolder1/memory_handler.c:54: osMemoryBlock* pMsgBuf = osCreateMemoryBlock(argv[1], sizeof(osMemoryBlock));
subfolder1/memory_handler.c:116:void *osCreateMemoryBlock(const char* filePath,
subfolder1/memory_creater.c:47:void *osCreateMemoryBlock(const char* filePath, unsigned size);
subfolder1/memory_creater.c:54: osMemoryBlock* pMsgBuf = osCreateMemoryBlock(argv[1], sizeof(osMemoryBlock));
shared_memory_writer.c:33: int *niz = osCreateMemoryBlock(argv[1], size);
Извлечение данных файла журнала с помощью Grep
Допустим, вы заметили, что ваша машина зависает слишком долго перед
выключением, но вы не знаете, почему. Вы можете проверить свой системный
журнал, хотя он содержит миллионы строк. Если у нас есть
приблизительное представление о том, когда это произошло, grep
может
помочь нам легко найти эту информацию:
$ grep -E -n "May 27 19:2[0-1]:[0-9]{2} *" /var/log/syslog
Это выведет все, что было записано в журналах системы за указанный нами период. Он также распечатает номера строк для нашего удобства, если мы захотим позже изучить весь файл.
Естественно, этот файл журнала у всех разный, но он может выглядеть примерно так:
1005:May 27 19:21:59 machine kernel: [236041.840122] mce: CPU0: Core temperature above threshold, cpu clock throttled (total events = 2918)
1006:May 27 19:21:59 machine kernel: [236041.840125] mce: CPU0: Package temperature above threshold, cpu clock throttled (total events = 3753)
1014:May 27 19:21:59 machine kernel: [236041.841137] mce: CPU4: Core temperature/speed normal
1016:May 27 19:21:59 machine kernel: [236041.841139] mce: CPU4: Package temperature/speed normal
Этот метод можно применить практически к любому типу файла журнала - просто убедитесь, что предварительно проверили его формат, а затем переходите к построению точного регулярного выражения.
Заключение
В этой статье мы рассмотрели основы одного из самых известных доступных инструментов командной строки. Это очень эффективный способ поиска в любом тексте или извлечения из него данных.
На примерах его использования в реальной жизни становится очевидным, что
grep
может творить чудеса во многих областях, представляющих интерес,
в сочетании с некоторыми другими инструментами командной строки и
передовыми знаниями по построению регулярных выражений.