Перейти к основному содержимому
Отдельные темы01ГЛАВА 01Регулярные выражения для grep404error

Регулярные выражения для grep

Прочитано0%
Квизы0/9
Тренажёрыне пройден0/2
Уровень 1 · Новичок

Зачем это нужно​

В главе про терминал мы обходились командами над файлами целиком: показать, скопировать, удалить. Но на чемпионате и в работе постоянно нужна операция потоньше — найти в большом файле нужные строки. Лог веб-сервера на десять тысяч строк, в котором нужно выловить только ошибки. Конфиг, в котором нужно найти строку с портом. Чужой проект, в котором нужно понять, где вообще используется функция с таким-то именем. Листать глазами — не вариант; для этого есть grep.

grep расшифровывается как global regular expression print — «напечатать по глобальному регулярному выражению». Название выдаёт главное: сила grep не в поиске точного текста (это он тоже умеет), а в поиске по шаблону — регулярному выражению (regular expression, коротко regex). Шаблон описывает не конкретную строку, а правило: «строка, начинающаяся с цифры», «слово error в любом регистре», «три цифры, дефис, ещё две цифры». Один раз научившись читать и писать такие правила, ты получаешь суперспособность, которая работает далеко за пределами grep: те же регулярные выражения понимают редакторы кода, языки программирования и половина инструментов командной строки.

Эта страница — углубление: базовый маршрут от неё не зависит, но перед ней стоит пройти главу «Linux и терминал», чтобы команды, флаги и конвейер | уже не были новостью.

grep без регулярок: точный текст и главные флаги​

Начнём с того, что regex ещё не требует. В простейшей форме grep получает текст для поиска и файл — и печатает все строки, в которых этот текст встречается:

grep error app.log
GREP · ИДЁТ ПО СТРОКАМ ОДНА ЗА ДРУГОЙшаблон: errorapp.log2026-01-02 INFO start2026-01-02 INFO ready2026-01-03 error: timeout2026-01-04 INFO ok2026-01-05 INFO doneнапечатано:…error: timeoutgrep сравнивает с шаблоном каждую строку отдельно — совпавшую печатает, остальные молча пропускает
Grep сравнивает с шаблоном каждую строку файла отдельно — совпавшую печатает, остальные молча пропускает.

Нажми на части команды посложнее — с флагами, которые понадобятся в первую очередь:

Нажми на часть выражения

Флаги, без которых не живёт ни один день с grep:

  • -i — игнорировать регистр. Ошибки в логах пишут кто во что горазд: Error, ERROR, error — с -i найдутся все.
  • -n — показывать номера строк. Нашёл строку — сразу знаешь, куда идти в редакторе.
  • -v — инвертировать поиск: напечатать строки, где шаблона нет. grep -v '#' config.txt — конфиг без строк с решёткой.
  • -c — не печатать строки, а посчитать их: grep -c error app.log ответит одним числом.
  • -w — искать только целые слова: grep -w log найдёт log, но не login и не catalog.
  • -r — искать рекурсивно по всем файлам каталога: grep -r TODO src/ — все заметки TODO во всём проекте.

И приём из главы про терминал: grep умеет читать не только файлы, но и вывод другой команды через конвейер — cat app.log | grep error даёт тот же результат, а history | grep ssh найдёт все команды ssh, которые ты набирал.

Отвечено вопросов: 0 из 4
Чем grep -w error отличается от grep error?
Нужно увидеть конфиг БЕЗ строк-комментариев (в них есть символ #). Какая команда подойдёт?
Что напечатает grep -c error app.log?
Как найти все упоминания TODO во всех файлах проекта сразу?
Отвечено верно: 0 из 4

Точка, экранирование и классы символов​

Теперь сама магия. В регулярном выражении часть символов — не буквы, а операторы. Первый из них — точка: . означает «ровно один любой символ». Шаблон v.sion найдёт и vision, и version... стоп, нет: version не найдёт — между v и sion там два символа. А вот v..sion — найдёт version, потому что двум точкам соответствуют er.

Отсюда сразу неприятный сюрприз: если искать точку буквально — например, версию 1.5 или расширение .txt — шаблон 1.5 найдёт заодно 105, 1x5 и вообще «1, что угодно, 5». Чтобы оператор снова стал обычным символом, его экранируют обратной косой чертой: 1\.5 — это уже строго «единица, точка, пятёрка».

Второй оператор — класс символов: квадратные скобки со списком допустимых символов внутри. [abc] — «один символ: a, b или c». Внутри класса работает диапазон через дефис: [0-9] — любая цифра, [a-z] — любая строчная латинская буква, [A-Za-z0-9] — буква или цифра. А если первый символ в скобках — крышка, класс инвертируется: [^0-9] — «один любой символ, кроме цифры».

ТОЧКА И КЛАССЫ СИМВОЛОВ.любой один символсоответствует: v, 1, ?\.буквально точкасоответствует: только .[0-9]один символ из наборасоответствует: 0…9[^0-9]НЕ из наборасоответствует: a, !, _точка без экрана — оператор; с обратной косой — обычный символ; крышка первой в скобках — инверсия
Точка без экрана — любой символ; с обратной косой — буквально точка; квадратные скобки задают набор, крышка первой — инверсию.
Нажми на часть выражения

Обрати внимание на кавычки вокруг шаблона. Символы *, [, ], $ — операторы не только для grep, но и для самой оболочки bash (вспомни подстановку *.txt из главы про терминал). Одинарные кавычки говорят оболочке: «не трогай, передай как есть». Правило простое: шаблон со спецсимволами — всегда в одинарных кавычках; за привычку брать в кавычки вообще любой шаблон никто не осудит.

Якоря: начало и конец строки​

По умолчанию grep ищет совпадение где угодно в строке. Якоря (anchors) прибивают шаблон к месту: ^ — к началу строки, $ — к концу.

  • grep '^From' mail.txt — строки, начинающиеся с From; письмо, где From встречается в середине текста, не подойдёт.
  • grep 'done$' tasks.txt — строки, заканчивающиеся на done.
  • grep '^$' file.txt — пустые строки: начало строки, сразу за ним конец, между ними ничего. А grep -c '^$' file.txt их посчитает.
  • grep -v '^#' config.txt — фильтр комментариев поточнее, чем раньше: выкидываются только строки, где # стоит первым символом, а строки, где решётка встречается в середине, остаются.

Якорь — не символ строки, а позиция: ^ не ищет никакую «крышку» в тексте, он утверждает «здесь начало». Поэтому шаблон ^$ находит строку нулевой длины, хотя в нём два символа.

ЯКОРЯ · ПОЗИЦИЯ, А НЕ СИМВОЛgrep '^From' mail.txtFrom: Alice^ — вот здесь, перед первым символомgrep 'done$' tasks.txttask done$ — вот здесь, сразу после последнего^ и $ не совпадают ни с одним символом строки — они утверждают позицию
^ и $ не совпадают ни с одним символом строки — они утверждают позицию: здесь начало, здесь конец.
Отвечено вопросов: 0 из 4
Что найдёт шаблон 1.5 без экранирования?
Что означает класс [^0-9]?
Как найти пустые строки в файле?
Почему шаблон стоит брать в одинарные кавычки?
Отвечено верно: 0 из 4

Квантификаторы: сколько раз повторить​

Последний кирпич — квантификаторы (quantifiers), операторы «сколько раз повторяется предыдущий элемент»:

  • * — ноль или больше раз. ab*c найдёт ac, abc, abbbbc.
  • + — один или больше раз. ab+c найдёт abc и abbbc, но уже не ac.
  • ? — ноль или один раз. colou?r найдёт и color, и colour.
  • {n} — ровно n раз, {n,m} — от n до m раз. [0-9]{4} — ровно четыре цифры подряд.

Квантификатор относится к одному предыдущему элементу — символу или классу. [0-9]+ — «одна или больше цифр», а 10* — это «1, а потом сколько угодно нулей» (10, 100, 1000...), не «десять сколько-то раз».

Тонкость, о которую спотыкаются все: исторически у grep два диалекта регулярных выражений. В базовом (BRE — basic regular expressions), который включён по умолчанию, операторы +, ?, {} нужно писать с обратной косой: \+, \?, \{4\}. В расширенном (ERE — extended regular expressions) они работают как есть, но нужен флаг -E. Проще всего запомнить одно правило: пользуешься +, ? или фигурными скобками — добавляй -E и пиши по-человечески.

Нажми на часть выражения
АНАТОМИЯ ШАБЛОНА · ^[0-9]{3}-[0-9]{2}^якорьначало строки[0-9]{3}класс + квантификатортри цифры подряд-литералобычный дефис[0-9]{2}класс + квантификаторещё две цифрыgrep -E '^[0-9]{3}-[0-9]{2}' codes.txt — четыре разных элемента сцепляются в один шаблон
Четыре разных элемента сцепляются в один шаблон: позиция, повторяемый класс, обычный символ, снова класс с повторением.
Отвечено вопросов: 0 из 4
Что найдёт шаблон ab*c?
Чем + отличается от *?
Почему grep '[0-9]{4}' может ничего не найти там, где grep -E '[0-9]{4}' находит?
К чему относится квантификатор в шаблоне 10*?
Отвечено верно: 0 из 4

Жадность и типичные ошибки новичков​

Здесь стоит остановиться и разобрать грабли, на которые наступают почти все, кто только начал писать шаблоны.

Точка вместо экранированной точки. Уже разбирали выше, но повторим отдельно, потому что это самая частая ошибка вообще: grep 192.168.1.1 file найдёт не только настоящий IP, но и 192x168y1z1 — мало ли что случайно окажется на месте точек в другом контексте. Правило простое: если символ в шаблоне должен означать ровно этот символ, а не оператор — экранируй. Для IP это особенно коварно: 192.168.1.1 выглядит невинно, но незаэкранированные точки делают шаблон куда шире, чем кажется на глаз.

Жадность * и .*. Квантификатор * (и +) — жадный (greedy): он захватывает максимально длинный кусок текста, какой ещё позволяет найти совпадение целиком. grep -o '".*"' data.txt в строке "a", "b", "c" найдёт не три отдельные пары кавычек, а одну — "a", "b", "c" целиком, потому что .* растягивается от первой кавычки до последней в строке, а не до ближайшей. Новичок ждёт «сначала кавычка, что угодно, снова кавычка — покороче», а получает «всё, что нашлось между самой первой и самой последней кавычкой».

Важно

.* — это не «любой текст между двумя метками», а «максимально длинный текст, при котором шаблон ещё матчится». В строке с несколькими одинаковыми разделителями жадность почти всегда даёт не тот результат, который ты ожидал. Проверяй на реальных данных, а не только в голове.

В обычном grep укротить жадность нечем — ленивый (non-greedy, *?) квантификатор в базовом и расширенном диалектах не существует. Он появляется только в PCRE, то есть с флагом -P: grep -oP '".*?"' data.txt на той же строке уже честно найдёт "a", "b" и "c" по отдельности — *? берёт минимально возможный кусок. Если -P недоступен, спасаются классом-исключением: grep -o '"[^"]*"' data.txt — «кавычка, затем сколько угодно символов, которые не кавычка, затем кавычка» — работает без -P и даёт тот же результат, потому что явно запрещает квантификатору перепрыгивать через границу.

Регистр по умолчанию учитывается. grep Error log не найдёт error и не найдёт ERROR — регистр важен, пока не добавлен -i. Логи и код пишут вперемешку, и забытый -i — причина половины «команда ничего не нашла, хотя я точно видел эту строку».

BRE и ERE — не варианты одного языка, а разные грамматики. Причина исторической путаницы в том, что POSIX закрепил два набора правил для одних и тех же символов. В BRE операторы +, ?, |, {}, круглые скобки группировки без косой черты — это обычные символы: grep 'a{3}' file в базовом диалекте ищет буквально текст a{3} (с фигурными скобками!), а вовсе не «три буквы a». В ERE ровно наоборот: без косой черты это операторы, а с косой чертой (\{, \+) они становятся обычным текстом. Это не баг и не недосмотр — так исторически развивались две ветки одного стандарта, и grep до сих пор поддерживает обе ради обратной совместимости со скриптами 1980-х. Практический вывод уже звучал: определился, что нужны +/?/{}/группы — сразу пиши -E и не путайся, какие символы куда экранировать.

Отвечено вопросов: 0 из 3
Строка: "a", "b", "c". Что найдёт grep -o '".*"' на этой строке?
Как получить именно три отдельных совпадения "a", "b", "c" без флага -P?
Диалект BRE (базовый, по умолчанию). Что найдёт grep 'a{3}' file, если в файле есть строка aaa и строка a{3}?
Отвечено верно: 0 из 3

Группы, обратные ссылки и границы слов​

Круглые скобки в ERE (grep -E) — не только группировка приоритета, но и способ захватить часть совпадения, чтобы сослаться на неё дальше в этом же шаблоне. Такая ссылка называется обратной (backreference) и пишется \1, \2 — номер по порядку открывающих скобок.

Классика применения — найти повторяющееся слово: grep -E '\b([a-z]+) \1\b' text.txt найдёт строки вроде the the cat — где одно и то же слово написано подряд дважды. Здесь ([a-z]+) захватывает слово в группу 1, а \1 требует, чтобы дальше в строке встретилось точно то же самое, что попало в группу — не просто «ещё одно слово из строчных букв», а именно повтор.

Обратные ссылки работают и в BRE — там группы пишутся \(...\), а ссылка всё равно \1: grep '\(rm -rf\) .*\1' — надуманный пример, но принцип тот же.

\b — ещё один якорь, только не «начало/конец строки», а граница слова: место, где буквенно-цифровой символ соседствует с небуквенным (или с началом/концом строки). grep -w log из первой главы делает примерно то же самое, но \b гибче — им можно ограничить только один край: grep 'log\b' не найдёт login, но найдёт catalog (там log не в начале слова, а \b стоит только справа). GNU grep понимает \b во всех диалектах, хотя формально это расширение, а не часть POSIX.

Круглые скобки в ERE захватывают часть совпадения в пронумерованную группу; \1 внутри того же шаблона ссылается на то, что реально попало в группу, а не повторяет узор.

POSIX-классы [[:alpha:]], [[:digit:]], [[:alnum:]], [[:space:]], [[:upper:]], [[:lower:]] — работают только внутри квадратных скобок, замена диапазонам вроде [a-z]. Разница не только в длине записи: диапазон [a-z] — это буквально «байты между кодом a и кодом z» в текущей локали, а [[:alpha:]] — «то, что данная локаль считает буквой». На системе с русской локалью [[:alpha:]] подхватит и кириллицу, а [a-z] — никогда, что бы ни стояло в LANG. Для чемпионатных задач, где ввод почти всегда латиница и цифры, разница не критична, но привычка пригодится на реальных проектах с интернационализацией.

Совет

Комбинация квадратных скобок тройная: внешние [...] — сам класс символов, внутренние [: :] — обёртка имени POSIX-класса. [[:digit:]] читается как «класс, содержащий POSIX-класс digit» — непривычно на вид, но это не опечатка.

PCRE вкратце: \d, \w и просмотр вперёд​

С флагом -P grep переключается на Perl-совместимые регулярные выражения — тот же диалект, что лежит в основе regex в Python, JavaScript, Java и большинстве современных языков. Разница с BRE/ERE не только в сокращениях, но и в целом наборе возможностей, которых у POSIX-диалектов просто нет.

Сокращённые классы. Вместо [0-9] и [[:digit:]] можно писать \d — «цифра». Вместо [A-Za-z0-9_] — \w — «символ слова» (буква, цифра или подчёркивание). \s — пробельный символ, короткая замена [[:space:]]. Заглавная версия инвертирует: \D — не цифра, \W — не символ слова, \S — не пробел.

grep -oP '\d{3}-\d{2}-\d{2}' codes.txt

Тот же результат, что у grep -E '[0-9]{3}-[0-9]{2}-[0-9]{2}', но короче — и это уже тот самый синтаксис, который повторится в коде на Python или JS.

Ленивые квантификаторы. Уже встречались выше: *?, +?, ?? — «минимально возможное количество повторений» вместо максимального. Работают только с -P.

Просмотр вперёд (lookahead). (?=...) — «дальше должно идти то-то, но само оно не входит в совпадение». Например, найти число, за которым сразу следует знак %, но захватить только само число, без процента:

grep -oP '\d+(?=%)' report.txt

В строке CPU: 87%, RAM: 42% это вернёт 87 и 42 — без символа %, потому что просмотр вперёд проверяет следующий символ, не включая его в результат. Есть и отрицательный просмотр вперёд (?!...) — «дальше НЕ должно идти то-то» — и симметричные просмотры назад (?<=...), (?<!...).

Важно

-P — не часть переносимого POSIX-набора: он есть в GNU grep (Linux) и не гарантирован на других системах (например, в BSD-версии grep на macOS его нет по умолчанию). Для учебных задач и на своей машине пользуйся смело, но в скрипте, который должен работать где угодно, закладывайся на BRE/ERE или ставь ripgrep/GNU grep явно.

Отвечено вопросов: 0 из 2
Строка: "CPU: 87%, RAM: 42%". Что вернёт grep -oP '\d+(?=%)' на этой строке?
Какой флаг обязателен, чтобы использовать \d, \w и просмотры вперёд/назад в grep?
Отвечено верно: 0 из 2

grep -F: когда регулярка не нужна вообще​

Иногда шаблон, который нужно найти, сам по себе набит символами, которые regex считает операторами — например, вызов функции calculate(x, y) или путь C++/std::vector. Экранировать каждую скобку и точку вручную — источник опечаток на ровном месте. Для этого есть -F (fixed strings, то же самое, что устаревшая команда fgrep): он выключает интерпретацию regex полностью, и весь шаблон читается буквально, символ в символ.

grep -F 'calculate(x, y)' src/*.js

Без -F пришлось бы писать grep 'calculate(x, y)' и — сюрприз — это тоже сработало бы, потому что круглые скобки в BRE без экранирования не операторы (это верно только для базового диалекта!). Но стоило бы кому-то в команде привычно добавить -E для другого шаблона по соседству и скопировать этот же поиск — как скобки внезапно превратились бы в группировку, и совпадение сломалось бы без видимой причины. -F снимает вопрос полностью: с ним неважно, сколько в шаблоне скобок, точек и звёздочек — всё воспринимается буквально, при любом диалекте и при любом флаге рядом.

-F — это ещё и самый быстрый режим grep: без разбора regex поиск подстроки работает заметно быстрее на очень больших файлах, если шаблон заведомо не содержит операторов.

Совет

Правило простое: если шаблон — точный текст без единого regex-оператора (имя функции, точный путь, готовая строка ошибки, скопированная из лога) — -F избавляет от риска экранирования и работает быстрее. Если в шаблоне нужна хоть одна метасимвольная возможность regex — точка как «любой символ», класс, квантификатор — -F не подходит, он и это прочитает буквально.

Отвечено вопросов: 0 из 2
Нужно найти в коде точный вызов calculate(x, y) — без разбора regex, чтобы скобки не считались операторами группировки. Какой флаг для этого?
Почему на -F стоит переключаться, а не просто аккуратно экранировать спецсимволы вручную?
Отвечено верно: 0 из 2

Контекст вокруг совпадения и поиск по дереву проекта​

До сих пор grep печатал ровно совпавшую строку. На практике часто нужна пара строк вокруг неё — увидеть, в каком блоке кода или лога находится совпадение:

  • -A N (after) — N строк после совпадения.
  • -B N (before) — N строк до совпадения.
  • -C N (context) — по N строк с обеих сторон разом.
grep -C 2 'panic' service.log

Это покажет каждую строку с panic вместе с двумя строками до и двумя после — обычно этого хватает, чтобы понять, что происходило вокруг сбоя, не открывая лог целиком.

Ещё пара флагов, без которых не обойтись при работе с реальным репозиторием, а не с одним файлом:

  • -l (list) — вместо самих строк напечатать только имена файлов, где шаблон нашёлся хотя бы раз. grep -rl TODO src/ — список файлов с заметками TODO, без самих заметок.
  • -L — наоборот, файлы, где шаблон не встретился ни разу.
  • --include='*.py' / --exclude='*.min.js' — при рекурсивном поиске (-r) ограничить, какие файлы вообще смотреть по маске имени. grep -r --include='*.js' --exclude-dir=node_modules TODO . — искать TODO только в .js-файлах, не заходя в node_modules.
  • --exclude-dir=DIR — та же идея, но по каталогам; .git и node_modules в рекурсивном поиске почти всегда стоит исключать явно.
Нажми на часть выражения

Живые сценарии: зачем это в реальной работе​

Регулярки ради регулярок быстро надоедают. Вот пять ситуаций, где grep реально экономит время — примерно то, чем придётся заниматься на чемпионате и потом на работе.

1. Собрать все TODO и FIXME по проекту. Классика: разработчики оставляют заметки прямо в коде, и раз в спринт их нужно собрать в один список.

grep -rn -E 'TODO|FIXME' --include='*.js' --include='*.py' src/

-E 'TODO|FIXME' — оператор | (тоже требует -E) означает «или»: строка подходит, если содержит любую из альтернатив. -n сразу даёт номер строки, чтобы не искать глазами.

2. Найти все места, где используется функция, перед рефакторингом. Прежде чем переименовать или изменить сигнатуру функции, нужно понять, где она вообще вызывается — иначе легко что-то сломать втихую.

grep -rn '\bcalculateTotal(' --include='*.js' src/

\b перед именем не даёт зацепить recalculateTotal( — важно, когда имя функции может быть подстрокой другого имени. Открывающая скобка в конце шаблона отсекает случаи, где слово calculateTotal встретилось в комментарии без вызова.

3. Найти вероятную утечку секрета в коде. Один из самых частых типов уязвимостей — забытый в коде API-ключ или пароль. Прежде чем коммитить, стоит прогнать грубую проверку:

grep -rniE '(api[_-]?key|secret|password|token)\s*=\s*["\047][a-z0-9]{16,}["\047]' --include='*.js' --include='*.py' .

Смысл: ищем строку вида api_key = "AKIA1234567890ABCDEF" — слово-маркер (api_key, secret, password, token, регистр любой из-за -i), затем знак равенства, затем длинная строка в кавычках. \047 — восьмеричный код одинарной кавычки: так её можно вставить внутрь шаблона, который сам взят в одинарные кавычки, не разрывая их раньше времени. Это грубый эвристический фильтр, а не гарантия — он даст ложные срабатывания и пропустит хитрые случаи, но как быстрый предохранитель перед git push вполне годится. Для серьёзной проверки существуют специализированные инструменты (gitleaks, trufflehog) — они умеют то же самое надёжнее.

Важно

Никогда не проверяй такой шаблон на реальных боевых ключах в общем логе или чате — сам факт, что строка «похожа на секрет», уже повод её ротировать, если она куда-то утекла. Здесь и везде на платформе примеры ключей — вымышленные.

4. Найти файлы по содержимому, а не по имени, и что-то с ними сделать. grep находит строки, но часто нужно передать файлы дальше — например, в sed для массовой замены. Связка grep -l + xargs:

grep -rl 'oldFunctionName' --include='*.js' src/ | xargs grep -c 'oldFunctionName'

Здесь grep -rl выдаёт список файлов, где встречается oldFunctionName, а xargs подставляет каждое имя файла как аргумент второй команде — которая уже считает, сколько раз имя встретилось в каждом файле. Тот же приём работает с find: find . -name '*.log' | xargs grep -l ERROR — сначала find отбирает файлы по маске имени, потом grep фильтрует их по содержимому.

5. Быстро прикинуть уровень ошибок в проде. Утилита -c вместе с конвейером даёт мгновенную сводку:

grep -c ERROR *.log

Печатает по одной строке на файл: имя файла и количество ошибок в нём — без единого лишнего действия.

grep в скриптах: код возврата и другие мелочи​

Когда grep вызывается не руками, а из другого скрипта, важны детали, которые в интерактивной работе почти не замечаешь.

Код возврата. grep не только печатает строки — он ещё и возвращает код завершения (exit status): 0, если совпадение нашлось, 1, если не нашлось, 2, если случилась ошибка (например, файла не существует). Это делает grep готовым строительным блоком для условий в скриптах:

if grep -q 'ERROR' service.log; then
echo "В логе есть ошибки"
fi

Флаг -q (quiet) — важная деталь: он подавляет обычный вывод grep и оставляет только код возврата. Без -q команда сработала бы так же с точки зрения if, но заодно напечатала бы в терминал саму найденную строку — лишний шум, если тебе нужен только факт «нашлось / не нашлось».

-f pattern-file — шаблоны для поиска можно держать не в командной строке, а в отдельном файле, по одному на строку: grep -f patterns.txt log.txt найдёт строки, подходящие под любой из шаблонов файла. Удобно, когда список того, что нужно найти, длинный или пересобирается динамически другим скриптом.

-H и -h — управляют тем, показывать ли перед каждой строкой имя файла. Если передать grep несколько файлов, он сам добавляет префикс имя_файла: перед каждой строкой; -h этот префикс убирает (полезно, когда файл всё равно один и имя не нужно), -H — принудительно включает его, даже если файл всего один (полезно в скриптах, где вывод потом парсится и формат должен быть предсказуемым независимо от числа файлов).

Бинарные файлы. По умолчанию grep, наткнувшись на файл, который выглядит не текстовым (например, скомпилированный .so или картинка), не станет вываливать в терминал мусор — он напечатает binary file matches и перейдёт дальше. Если действительно нужно искать текст внутри бинарного файла построчно как есть — -a заставляет grep обращаться с ним как с текстом, а -I в рекурсивном режиме, наоборот, полностью пропускает бинарные файлы, даже не сообщая о совпадении.

Совет

Привычка проверять код возврата, а не парсить вывод команды глазами — общий принцип автоматизации, не только для grep. grep -q ... && действие — компактная замена if, которая часто встречается в однострочниках и хуках.

Отвечено вопросов: 0 из 2
Нужно в скрипте проверить, есть ли в service.log слово ERROR, и при этом не печатать саму найденную строку в терминал — важен только факт. Как написать условие?
grep наткнулся на файл, похожий на бинарный, и напечатал "binary file matches" вместо самой строки. Что заставит grep всё-таки показать совпавшую строку как текст?
Отвечено верно: 0 из 2

Несколько шаблонов сразу и разбор прод-инцидента​

Ещё один частый вопрос: как искать «это ИЛИ то», не переключаясь в ERE. Два способа, разного вкуса.

-e — несколько независимых шаблонов в одной команде. grep -e ERROR -e WARN service.log найдёт строки с любым из двух шаблонов — фактически то же самое, что grep -E 'ERROR|WARN', но без переключения диалекта. Удобно, когда шаблоны и так пишутся по одному через -f или собираются скриптом.

\| в базовом диалекте. У GNU grep альтернатива | как GNU-расширение работает даже в BRE, если её экранировать: grep 'ERROR\|WARN' service.log. Это не стандарт POSIX (переносимость под вопросом), но на практике встречается часто — стоит уметь узнавать в чужом коде, даже если сам предпочитаешь -E.

Теперь соберём всё в один связный пример — как это обычно и происходит в работе: не «примени вот этот оператор», а «разберись, что случилось». Допустим, продовый сервис упал ночью, и в service.log за сутки — сотни тысяч строк. Задача: понять, что произошло, за несколько команд, не открывая файл целиком.

Шаг 1. Прикинуть масштаб. Сначала не читаем строки, а считаем:

grep -c -E 'ERROR|CRITICAL' service.log

Допустим, вышло 42 — не единичный сбой, а что-то системное.

Шаг 2. Понять, когда всё началось. Флаг -n даёт номера строк, а head/tail из главы про терминал сузят вывод до первых находок:

grep -n -E 'ERROR|CRITICAL' service.log | head -5

Первая найденная строка — это примерно момент, когда всё пошло не так.

Шаг 3. Посмотреть контекст вокруг самого первого сбоя. Здесь и пригождается -B/-A: важно увидеть, что происходило до первой ошибки — что могло её спровоцировать:

grep -B 5 -A 10 'CRITICAL' service.log | head -30

Шаг 4. Проверить, не разошлось ли это по всему коду. Если в логе мелькает конкретное имя функции или модуля — например, PaymentGateway — стоит сразу поискать, где оно вызывается в самом коде, а не только в логе:

grep -rn 'PaymentGateway' --include='*.py' --exclude-dir=tests src/

Шаг 5. Зафиксировать находку и оставить след для команды. grep -c по типам ошибок за смену — быстрая сводка для тикета инцидента, без единой ошибки, введённой глазами:

grep -oE 'ERROR|WARN|CRITICAL' service.log | sort | uniq -c

Здесь -o выводит только совпавшее слово (без остального текста строки) на каждой своей строке, а связка sort | uniq -c — приём из главы про конвейеры — группирует и считает повторы. Результат — три числа: сколько ERROR, сколько WARN, сколько CRITICAL, без единого лишнего клика по файлу.

Это и есть та самая суперспособность из начала главы: пять команд, каждая — одна-две секунды на выполнение, и вместо блуждания по стотысячестрочному логу — конкретный ответ.

Интересный факт

На практике почти никогда не пишут идеальный шаблон с первого раза. Обычная последовательность — сначала грубый grep -c, чтобы понять масштаб, потом сузить -n/-B/-A, чтобы найти нужное место, и только в конце — точный шаблон под конкретную задачу. Регулярка — не то, что придумывается заранее целиком, а то, что уточняется по ходу разведки.

Отвечено вопросов: 0 из 2
Нужно найти строки с ERROR или с WARN, не переключаясь на -E. Какая команда это сделает?
В разборе инцидента после grep -oE 'ERROR|WARN|CRITICAL' service.log стоит | sort | uniq -c. Зачем здесь эта связка?
Отвечено верно: 0 из 2

Практика: grep-квест в терминале​

Применим всё в учебном терминале. Честное предупреждение: grep здесь упрощённый — он ищет подстроку и не разбирает регулярные выражения, так что операторы из этой страницы отрабатывай потом в настоящем терминале. Зато флаги команд, конвейер и сама логика «найди иголку в стоге» — как настоящие.

В файле ~/logs/app.log среди сотни строк спрятана одна со словом ACCESS — в ней написан секретный код. Найди её c помощью grep (или cat app.log | grep ...), прочитай код и создай в домашней папке пустой файл, названный этим кодом: если код apple, то touch found_apple.txt.

Задание: grep-квест: выуди код из logs/app.log и создай файл found_<код>.txt
  • ○ found_cherry.txt
Учебный терминал. Введите help, чтобы увидеть список команд.
student@pgk:~$
Как это устроено под капотом
Никакого настоящего Linux здесь нет: файловая система — обычный JS-объект в памяти браузера, где папка — это вложенный объект, а файл — строка с содержимым. Команды ls, cd, mkdir, rm и остальные — функции, которые читают и меняют этот объект, а cp -r — просто глубокое копирование куска JSON. После каждой команды проверка квеста проходит по требуемым путям и смотрит, существуют ли они в этом дереве. Перезагрузишь страницу — «диск» создастся заново с нуля, так что ломать тут можно безнаказанно.

И закрепи руками написание настоящих шаблонов — с регистром, кавычками и косыми всё должно быть точно до символа:

Цель: точность ≥ 90%

grep -in error app.log

Шпаргалка​

ЗаписьЧто означаетПримерНайдёт
abcбуквально этот текстgrep error logстроки со словом error
.один любой символv..sionversion, но не vision
\.буквально точка1\.51.5, но не 105
[abc], [0-9]один символ из набора/диапазонаv[0-9]v1, v2...
[^0-9]один символ НЕ из набораid[^0-9]idx, но не id7
^, $начало / конец строки^From, done$позиция, не символ
^$пустая строкаgrep -c '^$' fпосчитать пустые
*предыдущий 0+ разab*cac, abc, abbc
+ (с -E)предыдущий 1+ разab+cabc, но не ac
? (с -E)предыдущий 0–1 разcolou?rcolor и colour
{n}, {n,m} (с -E)ровно n / от n до m раз[0-9]{4}четыре цифры подряд
-i / -n / -vбез регистра / с номерами / инверсияgrep -inv—
-c / -w / -rпосчитать / целые слова / рекурсивноgrep -rw log .—
-Eрасширенный диалект EREgrep -E '[0-9]{3}' codes.txtвключай для + ?
\bграница словаgrep 'log\b'log, catalog, но не login
(...), \1 (с -E)группа-захват и обратная ссылкаgrep -E '(\w+) \1'the the
[[:digit:]], [[:alpha:]]POSIX-классыgrep '[[:digit:]]\{3\}'три цифры, локале-независимо
-A N / -B N / -C NN строк после / до / вокругgrep -C 2 panic logсовпадение с контекстом
-l / -Lтолько имена файлов с / без совпаденийgrep -rl TODO src/список файлов
--include / --excludeфильтр по маске имени файла (с -r)grep -r --include='*.js'только нужные расширения
--exclude-dirне заходить в каталог (с -r)grep -r --exclude-dir=node_modulesобход без мусора
-qбез вывода, только код возвратаgrep -q ERROR log && ...0 — нашлось, 1 — нет
-f fileшаблоны из отдельного файлаgrep -f patterns.txt logсовпадение с любым из списка
-H / -hпринудительно показать / скрыть имя файлаgrep -h TODO *.jsбез префикса имени
-eещё один шаблон в той же команде (ИЛИ)grep -e ERROR -e WARNбез переключения на -E
-Fбез regex вообще, буквальный текстgrep -F 'calc(x, y)'никакие символы не операторы
\d, \w, \s (с -P)PCRE-сокращения: цифра / слово / пробелgrep -oP '\d{3}'как в Python/JS
Углубиться

Третий диалект: PCRE. Кроме BRE и ERE, у GNU grep есть флаг -P — Perl-совместимые регулярные выражения, самый богатый диалект: сокращения вроде \d (цифра) и \w (символ слова), ленивые квантификаторы, просмотры вперёд-назад. Именно PCRE-подобный синтаксис используют регулярки в Python, JavaScript и большинстве языков. В скриптах, которые должны работать на любой системе, на -P полагаться нельзя — он есть не во всех сборках grep, — но на своей машине это самый удобный режим.

POSIX-классы. Внутри квадратных скобок работают именованные классы: [[:digit:]] — то же, что [0-9], [[:alpha:]] — буквы, [[:space:]] — пробельные символы. Выглядят громоздко, но корректно работают с любыми локалями и алфавитами — в отличие от диапазона [a-z], который про кириллицу ничего не знает.

egrep и fgrep. В старых скриптах встречаются команды egrep (то же, что grep -E) и fgrep (то же, что grep -F — искать буквально, вообще без регулярных выражений). Они объявлены устаревшими: POSIX рекомендует формы с флагами, и новые скрипты стоит писать именно так.

-o — показать только совпадение. Обычно grep печатает строку целиком. Флаг -o печатает только совпавший кусок, каждое совпадение на своей строке: grep -oE '[0-9]+' log.txt вытащит из файла все числа. В связке с wc -l — готовый счётчик совпадений (не строк, как -c, а именно совпадений).

grep против ripgrep. На современных машинах (в том числе на многих чемпионатных стендах) рядом с grep часто стоит rg (ripgrep) — написанный на Rust инструмент с той же идеей, но заметно быстрее на больших деревьях каталогов и с более удобными настройками по умолчанию: rg сам игнорирует .git, уважает .gitignore и по умолчанию рекурсивен, то есть rg TODO уже эквивалентно grep -r TODO ., только без флага. Синтаксис шаблонов у него — PCRE-подобный, ближе к тому, что использует -P у grep. Знать grep всё равно обязательно: он есть на любой системе без установки, входит во все стандарты POSIX и остаётся тем, что спросят на экзамене и что точно найдётся на чужом сервере, куда rg никто не поставил.

Отличие POSIX-классов от диапазонов на практике. [a-z] работает только в локалях, где алфавит устроен как непрерывный диапазон байтовых кодов a-z — это верно для английского, но не для многих других языков и не для смешанных данных. [[:alpha:]] спрашивает у текущей локали «что тут вообще считается буквой» и работает предсказуемо в любой из них — цена этому предсказуемости — более громоздкая запись.

Дальше по теме:

  • GNU Grep Manual — Regular Expressions — официальное руководство: точное описание BRE, ERE и PCRE со всеми операторами
  • man 7 regex — справка по регулярным выражениям POSIX, общая для grep, sed и других утилит

Сводная проверка по всей главе: 14 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Что должен уметь​

  • Искать текст в файле и в выводе другой команды, применяя флаги -i, -n, -v, -c, -w, -r по назначению.
  • Объяснять, почему шаблон берут в одинарные кавычки и что случится без них.
  • Читать и писать шаблоны с точкой, экранированием, классами [...] и инвертированными классами [^...].
  • Прибивать шаблон якорями ^ и $ — в том числе находить пустые строки и строки-комментарии.
  • Пользоваться квантификаторами *, +, ?, {n,m} и знать, когда нужен флаг -E.
  • Отличать диалекты BRE и ERE и объяснять, почему grep '{4}' и grep -E '{4}' ведут себя по-разному.
  • Понимать жадность */+ и уметь укрощать её классом-исключением [^...] или, если доступен -P, ленивым квантификатором.
  • Использовать группы захвата и обратные ссылки \1 для поиска повторов, а границу слова \b — для точечного ограничения совпадения.
  • Показывать контекст вокруг совпадения флагами -A/-B/-C и работать с деревом проекта через -l, --include, --exclude, --exclude-dir.
  • Собирать рабочие связки: grep -r + find/xargs для массовых операций, грубый шаблон для поиска утекших секретов перед коммитом.
  • Пользоваться кодом возврата (grep -q в условиях) и флагами для скриптов: -f, -H/-h, -a.
  • Искать по нескольким шаблонам сразу через -e и знать, когда достаточно -F вместо полноценного regex.
  • Понимать, чем PCRE (-P, \d, \w, просмотры вперёд) отличается от POSIX-диалектов и когда на него можно полагаться.

Куда дальше: проверенные ресурсы​

  • GNU Grep Manual — первоисточник: все флаги и все три диалекта регулярных выражений в одном документе.
  • man grep (Ubuntu) — официальная man-страница: краткая версия того же, всегда под рукой по команде man grep.
  • gitleaks — если понравилась идея грубого поиска секретов из раздела про живые сценарии: это тот же принцип, но с готовой библиотекой шаблонов под конкретные форматы ключей (AWS, GitHub, Slack и десятки других) и встраиванием в pre-commit хук.
regex101.com
Интерактивная песочница: вставляешь шаблон и текст, видишь совпадения подсвеченными и с пояснением каждого оператора. Выбирай flavor PCRE и проверяй свои шаблоны перед запуском. Сайт запрещает встраивание — открывается в новой вкладке.
RegexOne
Пошаговый интерактивный курс регулярных выражений с упражнениями, от точки до групп. Сайт запрещает встраивание — открывается в новой вкладке.
Интересный факт

Имя grep — буквально команда из редактора ed 1970-х: g/re/p, «глобально найти по regular expression и напечатать». Кен Томпсон вынес её в отдельную программу — и она пережила сам редактор на полвека.

Видео по теме

ВидеоЛинукс для Девопс | Команда grep | Регулярные Выражения regexADV-IT
ВидеоНе бойтесь регулярных выражений. Regex за 20 минут!WebDev с нуля. Канал Алекса Лущенко
ВидеоRegular Expressions (RegEx) in 100 SecondsFireship
ВидеоRegular Expressions in Linux | Intro to GREP and RegExKodeKloud
ВидеоLinux Regular Expressions (Regex) Tutorial: grep, sed, & awk for BeginnersCodeLucky

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.