grep и регулярные выражения
| Тип | утилита командной строки |
|---|---|
| Автор | Кен Томпсон |
| Появилась | 1973 (Unix Version 4) |
| Название | от команды ed g/re/p |
| Реализации | GNU grep, BSD grep, BusyBox, ripgrep (rg) |
| Синтаксисы шаблонов | BRE, ERE (-E), PCRE (-P), фиксированные строки (-F) |
| Стандарт | POSIX |
grep — утилита командной строки Unix, которая выводит строки файлов (или стандартного ввода), соответствующие заданному шаблону. Шаблоны записываются регулярными выражениями — компактным языком описания множеств строк. grep — один из самых часто используемых инструментов в Linux: им ищут ошибки в логах, вызовы функций в коде, строки конфигурации и фильтруют вывод других программ в конвейерах.
Название происходит от команды текстового редактора ed g/re/p — «глобально найти по регулярному выражению (regular expression) и напечатать». Слово «грепнуть» давно стало глаголом в жаргоне программистов.
История
Утилиту написал Кен Томпсон, один из создателей Unix; она вошла в Unix Version 4 (1973). Томпсон и раньше занимался регулярными выражениями: в 1968 году он опубликовал алгоритм их компиляции в автомат, который и сегодня лежит в основе быстрых движков. Позже появились варианты egrep (расширенные выражения) и fgrep (фиксированные строки); теперь вместо них используют ключи grep -E и grep -F.[1] Синтаксис выражений и поведение grep закреплены в стандарте POSIX.[3]
Основные ключи
grep ERROR app.log # строки, содержащие ERROR
grep -i error app.log # без учёта регистра
grep -v DEBUG app.log # строки, НЕ содержащие DEBUG
grep -n 'def main' *.py # с номерами строк
grep -c ERROR app.log # только количество совпадений
grep -rn 'TODO' src/ # рекурсивно по каталогу
grep -rl 'api_key' . # только имена файлов
grep -w log file.txt # целое слово: log, но не login
grep -o '[0-9]\+ms' app.log # вывести только совпавшую часть
grep -A3 -B1 Traceback app.log # 3 строки после и 1 до совпадения
grep -F '[x]' list.md # шаблон как обычная строка, без спецсимволов
ps aux | grep -v grep | grep nginx # фильтр вывода другой команды
Код выхода grep — 0, если что-то найдено, 1, если ничего, и 2 при ошибке; на этом строятся проверки в скриптах (Shell и Bash): if grep -q ready status.txt; then ....
Регулярные выражения
Регулярное выражение — шаблон из обычных символов и метасимволов. Подробная теория — в статье Регулярные выражения; ниже — то, что нужно для grep.
| Конструкция | Смысл | Пример |
|---|---|---|
. |
любой символ | h.t — hat, hot, h1t |
^, $ |
начало и конец строки | ^# — строки-комментарии |
[abc], [^0-9] |
символ из набора / не из набора | [Ee]rror |
* |
0 и более повторений | ab*c |
+, ? |
1 и более / 0 или 1 (в ERE) | colou?r |
{n,m} |
от n до m повторений (в ERE) | [0-9]{3} |
| |
альтернатива (в ERE) | ERROR|FATAL |
( ) |
группировка (в ERE) | (ab)+ |
[[:digit:]], [[:space:]] |
классы символов POSIX |
BRE, ERE и PCRE
grep понимает несколько диалектов:[2]
- BRE (базовые, по умолчанию) —
+,?,|,{}и()считаются обычными символами, а для специального значения их нужно экранировать:\+,\(. Это источник постоянной путаницы. - ERE (расширенные,
grep -E) — метасимволы работают без обратной косой черты. Для новых шаблонов удобнее всего. - PCRE (
grep -P, только GNU grep) — синтаксис Perl:\d,\w, нежадные квантификаторы*?, просмотр вперёд и назад(?=...).[5] Совпадает с привычными выражениями Python и JavaScript.
grep -E '^(ERROR|WARN) ' app.log # ERE
grep -E '\b[0-9]{1,3}(\.[0-9]{1,3}){3}\b' access.log # похожее на IPv4
grep -oP '(?<=user_id=)\d+' app.log # PCRE: число после user_id=
Шаблон почти всегда стоит брать в одинарные кавычки: иначе оболочка сама раскроет *, $ и \ до того, как их увидит grep.
ripgrep и другие альтернативы
Для поиска по коду популярна утилита ripgrep (rg) Эндрю Галланта, написанная на Rust (2016). Она по умолчанию ищет рекурсивно, пропускает файлы из .gitignore и двоичные файлы, использует все ядра процессора и обычно заметно быстрее grep -r на больших репозиториях.[4] Для структурного поиска по коду применяют ast-grep, для поиска внутри JSON — jq, а для редактирования найденного — sed и awk.
rg 'fn main' -t rust # только Rust-файлы
rg -l TODO # файлы с TODO, с учётом .gitignore
Типичные ошибки
grep '*.log'—*в регулярном выражении означает «повторить предыдущее», а не «любые символы», как в шаблонах имён файлов оболочки.- Точка в IP-адресе или имени домена совпадает с любым символом; её нужно экранировать (
\.) или использовать-F. - Конструкция
cat file | grep xработает, но лишнийcatне нужен:grep x file. - В выводе
grep -rпо огромным каталогам (node_modules,.git) теряются нужные совпадения — помогает--exclude-dirили ripgrep.