Что такое grep и почему он базовый
grep — это утилита для поиска строк, соответствующих регулярному выражению, в текстовом потоке. Имя — акроним команды редактора ed: globally search for a regular expression and print matching lines. То есть g/re/p — выведи каждую строку, в которой совпадает regex.
Для DE grep — это первый инструмент при работе с логами:
# Сколько раз сегодня падал DAG?
$ grep "Task failed" /var/log/airflow/scheduler.log | wc -l
47
# Какие именно DAG?
$ grep "Task failed" /var/log/airflow/scheduler.log | awk '{print $NF}' | sort -u
load_orders
sync_dims
process_events
grep есть на любой UNIX-системе с самого начала — UNIX V4 (1973). Это POSIX-стандарт, есть везде: Linux, macOS, BSD, WSL, в minimal Docker-образах.
Базовый синтаксис
grep [OPTIONS] PATTERN [FILE...]
- Если файлы не указаны — читает stdin.
- PATTERN по умолчанию — basic regular expression (BRE). Это старый regex-стандарт с экранированием спец-символов:
\(,\+,\?,\|. - Возвращает exit code: 0 если что-то нашёл, 1 если нет, 2 если ошибка.
$ grep ERROR app.log
2026-05-13 12:34:56 ERROR: connection refused
2026-05-13 12:35:01 ERROR: timeout
$ echo "hello" | grep world
$ echo $?
1 # ничего не нашлось
Это exit code критичен в скриптах: можно делать if grep -q ERROR log; then alert; fi.
Основные флаги
80% реальных запросов обходятся этими флагами.
Работа с regex
По умолчанию grep использует BRE (basic regex). Чтобы получить ERE (extended regex) с привычным синтаксисом:
$ grep -E 'pattern' file
# или
$ egrep 'pattern' file # alias, deprecated в GNU grep 3.8+
Разница между BRE и ERE:
| Возможность | BRE (grep) | ERE (grep -E) |
|---|---|---|
. любой символ | . | . |
* 0+ повторов | * | * |
+ 1+ повторов | \+ | + |
? 0 или 1 | \? | ? |
| ` | ` альтернатива | | |
(...) группа | \(...\) | (...) |
{n,m} диапазон | \{n,m\} | {n,m} |
То есть в BRE привычные мета-символы надо экранировать, что обычно неудобно. Рекомендация: всегда -E (или используй ripgrep, который использует ERE-like синтаксис по умолчанию).
# ERROR или WARN
$ grep -E '(ERROR|WARN)' app.log
# Даты в формате YYYY-MM-DD
$ grep -E '[0-9]{4}-[0-9]{2}-[0-9]{2}' app.log
# Email (упрощённо)
$ grep -E '[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' contacts.txt
PCRE: -P
Если нужна полная Perl-совместимая regex (lookahead, lookbehind, named groups, \d, \w):
$ grep -P '\d{4}-\d{2}-\d{2}' app.log
$ grep -P '(?<=user=)\w+' auth.log # lookbehind: вывести user, не включая 'user='
$ grep -P '^(?!#)' config.txt # negative lookahead: строки, НЕ начинающиеся с #
PCRE мощнее, но на больших файлах медленнее ERE. На macOS BSD grep по умолчанию не поддерживает -P — нужен gnu-grep через Homebrew (brew install grep).
Якоря и классы символов
^pattern— строка начинается с patternpattern$— строка заканчивается на pattern^pattern$— вся строка ровно pattern[abc]— один из символов a, b или c[^abc]— НЕ a, не b, не c (отрицание класса)[a-z]— диапазон[[:digit:]]— POSIX-класс «цифры»[[:space:]],[[:alnum:]],[[:upper:]]— другие POSIX-классы
# Строки, начинающиеся с timestamp YYYY-MM-DD
$ grep -E '^[0-9]{4}-[0-9]{2}-[0-9]{2}' app.log
# Не пустые строки и не комментарии
$ grep -Ev '^#|^$' /etc/sshd_config
# IP-адреса (грубо)
$ grep -E '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' access.log
Множественные паттерны: -e и -f
# Несколько паттернов через -e
$ grep -e ERROR -e WARN -e FATAL app.log
# Паттерны из файла (один на строку)
$ cat patterns.txt
ERROR
WARN
FATAL
$ grep -f patterns.txt app.log
-f patterns.txt особенно мощно: можно держать список известных error-keywords в файле и обновлять отдельно от скрипта.
DE-паттерны с grep
1. Топ-10 ошибочных DAG за день
$ grep "Task failed" /var/log/airflow/scheduler.log \
| awk -F'dag_id=' '{print $2}' \
| awk -F',' '{print $1}' \
| sort | uniq -c | sort -rn | head -10
234 sync_dim_users
89 load_orders
45 process_events
...
2. Stack trace в Python-приложении (контекст)
$ grep -A 20 "Traceback" app.log # вывести traceback + 20 строк после
Стандартный Python-traceback занимает 5-15 строк — -A 20 выводит весь, плюс возможный лог-вывод после.
3. Найти конфиг-файлы с устаревшей настройкой
$ grep -rl 'use_legacy_sql' --include='*.cfg' /opt/airflow
/opt/airflow/dags/old_etl/airflow_local_settings.cfg
/opt/airflow/dags/team_a/connection_helper.cfg
--include='*.cfg' сужает recursive search только до .cfg файлов. -l показывает только имена. Удобно когда планируешь rewrite — сразу видишь список целей.
4. Грепнуть в gzip-логах
# Логи часто rotated и gzip'нуты:
$ ls /var/log/airflow/scheduler.log*
scheduler.log
scheduler.log.1
scheduler.log.2.gz
scheduler.log.3.gz
$ zgrep "ERROR" /var/log/airflow/scheduler.log.*.gz | head
zgrep — wrapper, который сам декомпрессирует .gz перед grep. Также есть bzgrep, xzgrep. На современных системах эти команды есть из коробки.
5. ripgrep (rg) — современный grep
# Установка
$ sudo apt install ripgrep # Ubuntu 26.04
$ brew install ripgrep # macOS
$ rg ERROR app.log
$ rg -i error app.log # case insensitive
$ rg 'ERROR|WARN' app.log # ERE по умолчанию
$ rg -t py 'TODO' # только .py файлы (type-detect)
$ rg --json PATTERN | jq ... # structured output
ripgrep (rg) написан на Rust, обходит .gitignore и binary-файлы по умолчанию, использует SIMD для скорости, понимает file types через расширения. На больших codebase в 5-50 раз быстрее grep.
Когда что использовать. Спойлер: ripgrep почти всегда быстрее и удобнее.
Скорость grep: фиксированные строки vs regex
Если pattern — просто фиксированная строка без regex-метасимволов, используй -F (fixed string):
$ grep -F '192.168.1.1' access.log
# vs
$ grep '192.168.1.1' access.log
# Второй интерпретирует точки как 'любой символ'!
# Результат: совпадёт и '192a168b1c1', что неверно.
-F (или fgrep) выключает regex parsing — точка остаётся точкой. Быстрее (не строит regex automaton) и семантически корректнее для поиска literal-строк.
Попробуй сам
- Создай тестовый лог и поищи в нём:
cat > /tmp/test.log <<EOF 2026-05-13 12:34:56 INFO starting service 2026-05-13 12:34:57 ERROR connection refused 2026-05-13 12:34:58 WARN retrying 2026-05-13 12:35:01 ERROR timeout EOF grep ERROR /tmp/test.log grep -c ERROR /tmp/test.log grep -E '(ERROR|WARN)' /tmp/test.log - Рекурсивный поиск TODO в своём проекте:
grep -rn TODO ~/Projects 2>/dev/null | head -20 - Найти строки с цифрами в начале:
grep -E '^[0-9]' /etc/group | head # Ничего — группы в /etc/group начинаются с букв - Найти конфиги без strict mode:
grep -L 'set -euo pipefail' /usr/local/bin/*.sh 2>/dev/null
macOS-различия
- BSD grep на macOS:
- Не поддерживает
-P(PCRE) — установиbrew install grepдля GNU-версии (ggrep) - Поддерживает
-E,-F,-i,-v,-n,-c,-l,-r— то же, что в GNU --include/--excludeесть, поведение немного отличается
- Не поддерживает
ripgrepчерезbrew install ripgrep— работает идентично Linux-версии.
Главное
grep PATTERN FILE— базовый синтаксис; ничего не нашёл -> exit code 1.- Ключевые флаги:
-i(case),-v(invert),-n(line nums),-c(count),-l(filenames),-r(recursive),-E(extended regex),-F(fixed string). - Используй
-Eдля нормального regex или-Pдля PCRE с lookahead/lookbehind. zgrepдля gzip-логов, без явной декомпрессии.- ripgrep (rg) — быстрее и удобнее, но не везде есть. В скриптах — POSIX grep.
-Fдля literal-поиска (быстрее и безопаснее regex).-A/-B/-Cдля контекста — незаменимо для stack-traces.