сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Linux и bash

grep, sed, awk и jq на практике

grep, sed, awk и разбор данных

Взял файл из шести строк, где три значения повторяются. Посчитал повторы командой подсчёта уникальных - получил шесть строк по одному разу каждая, то есть ничего не посчитал. Отсортировал перед подсчётом - получил честные 3, 2 и 1. Команда склеивает только СОСЕДНИЕ одинаковые строки, и об этом узнают обычно после того, как отчёт уже отправлен.

Стержень: у каждого инструмента есть ровно одно правило, которое ломает результат молча, и знать надо именно его.

// Формулировки: «посчитай топ ошибок в логе», «почему grep -c даёт не то число?», «как достать поле из JSON?»

Три инструмента и три молчаливые ловушки

Первая: подсчёт уникальных работает по соседним строкам, поэтому перед ним всегда идёт сортировка. Без неё результат выглядит правдоподобно и при этом неверен - у меня шесть строк вместо трёх значений.

Вторая: поиск с подсчётом считает СТРОКИ, а не совпадения. В моём файле слово встречалось четыре раза, но в двух строках: подсчёт строк дал 2, а подсчёт совпадений (вывести каждое совпадение отдельной строкой и посчитать их) дал 4. Если считаете события в логе, разница принципиальная.

// Третья: подсчёт строк командой wc считает переводы строки. Файл из трёх строк без перевода в конце даёт результат 2 - последняя строка не посчитана, потому что после неё нет перевода. Такое приезжает из выгрузок и из логов, обрезанных на середине. Честный подсчёт даёт поиск пустого шаблона с ключом подсчёта: он вернул 3.

подсчёт уникальных
склеивает только соседние одинаковые строки; нужна сортировка перед ним
совпадение против строки
в одной строке слово может встретиться несколько раз

Считать по колонкам

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

Доля ошибок: всего 5 запросов, из них 2 с кодом пятисотой группы, доля 40%. Время ответа: среднее 0,738 секунды, максимум 2,010. Топ адресов: первый адрес встретился трижды, остальные по разу. Всё это - обычные условия и накопление в переменных, никакой магии.

// Полезная привычка при разборе аварии: не тащить лог целиком в глаза, а сводить его к таблице «сколько чего». Три строки обработки отвечают на вопрос «что вообще происходит» быстрее, чем пятнадцать минут листания. И ещё: считайте долю, а не голое количество. «200 ошибок» ничего не значит, пока неизвестно, из скольких запросов.

колонка
поле строки, отделённое пробелами; в awk доступно по номеру
сводка вместо чтения
свести лог к таблице «сколько чего» до того, как читать глазами

JSON регулярками и правка файлов на месте

Достать поле из структурированных данных регулярным выражением (шаблоном для поиска по тексту) получается ровно до первого настоящего файла. JSON, в котором такие данные обычно и приезжают, - это текстовый формат записи вложенных структур: пары «имя поля - значение», списки, вложенные объекты. Я взял запись, где в имени есть экранированная кавычка: «Анна \"Аня\" Петрова». Регулярка вернула обрезок «Анна \"», потому что честно остановилась на первой кавычке. Разборщик вернул имя целиком. Второй случай в той же записи: рядом лежит поле с текстом, куда попало слово из шаблона поиска - регулярка в другом файле легко утащит именно его.

Вывод не «регулярки плохие», а простой: структуру разбирают разборщиком структуры. Для JSON это отдельный инструмент, который понимает вложенность, экранирование и типы; он же считает длину массивов и собирает выборки.

// Отдельная история - правка файла на месте. Замерил: у файла до правки был внутренний номер 789055, после правки стал 789124. То есть инструмент не изменил файл, а создал новый и переименовал его поверх старого. Последствий два. Первое: жёсткая ссылка на старый файл (второе имя того же файла на диске) остаётся со СТАРЫМ содержимым - проверил, ссылка после правки показывала прежний текст. Второе: программа, которая держала этот файл открытым, продолжит читать старую версию. Именно поэтому конфиг внутри контейнера, примонтированный одним файлом, после такой правки не обновляется.

разборщик структуры
инструмент, который понимает вложенность и экранирование, а не ищет по тексту
правка на месте
создание нового файла и переименование поверх старого; внутренний номер меняется

Как отвечать: «Посчитай топ ошибок в логе». Как это делается и где тут грабли?

Обычный конвейер: вытащить нужное поле, отсортировать, посчитать повторы, отсортировать по числу и взять верхние строки. Главная граблина в середине: подсчёт повторов склеивает только соседние одинаковые строки, поэтому без сортировки перед ним результат выглядит правдоподобно и при этом неверен - я проверял, шесть строк вместо трёх значений. Вторая граблина рядом: подсчёт с ключом счётчика считает строки, а не совпадения, а в одной строке слово может встретиться несколько раз, у меня было четыре совпадения в двух строках. Если данные разложены по колонкам, я вместо цепочки беру язык обработки текста и сразу считаю долю, а не количество: двести ошибок ничего не значат, пока неизвестно, из скольких запросов.

Ответ даёт рабочую цепочку и сразу называет два места, где она молча врёт. Плюс переход к доле вместо количества - это уже разговор про разбор аварий.

На чём валятся

  • Считают повторы без сортировки и получают правдоподобный неверный результат.
  • Путают число строк с числом совпадений при подсчёте событий.
  • Разбирают структурированные данные регулярками и ломаются на экранировании.
  • Правят файл на месте и удивляются, что жёсткая ссылка и открывший его процесс видят старое.
  • Считают количество вместо доли и не могут сказать, много это или мало.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 7, остальные разбираются в тренажёре.

  1. #dvo_text_tools1 / 5
    Считаем ошибки по коду в логе: awk '{print $9}' access.log | uniq -c. Числа получаются странные. Что не так?
    A)uniq -c считает строки, а не поля, нужен ключ -f
    B)uniq схлопывает только соседние повторы — перед ним нужен sort
    C)awk печатает поле с пробелом, и uniq видит разные строки
    D)uniq работает только с отсортированным по числу вводом, нужен sort -n
    показать ответ и разбор
    +B)uniq схлопывает только соседние повторы — перед ним нужен sort

    // разбор: uniq сравнивает строку с предыдущей и схлопывает подряд идущие дубли — на неупорядоченном потоке один и тот же код возврата даст десяток отдельных групп. Канон: awk '{print $9}' access.log | sort | uniq -c | sort -rn | head. Второй sort уже по числу, чтобы вытащить топ.

  2. #dvo_text_tools2 / 5
    Скрипт достаёт поле из JSON-ответа API через grep и sed. Что с этим не так на проде?
    A)grep не умеет работать с потоком от curl без буферизации
    B)sed портит юникод в ответе и ломает кодировку
    C)Регулярка не переживёт вложенность, порядок полей и переносы строк
    D)Такой разбор запрещён: API отдают gzip, а grep его не читает
    показать ответ и разбор
    +C)Регулярка не переживёт вложенность, порядок полей и переносы строк

    // разбор: JSON — дерево, а не строки: сервер вправе поменять порядок ключей, отдать всё одной строкой или разбить на много, добавить вложенность и экранированные кавычки. Регулярка на этом ломается молча и подсовывает мусор дальше по скрипту. jq разбирает структуру: jq -er '.data.id' и код возврата на отсутствующем поле — тот самый явный отказ, который нужен в автоматизации.

  3. #dvo_text_tools3 / 5
    Конфиг проброшен в контейнер как файл: -v /etc/app/conf.yml:/conf.yml. После sed -i на хосте контейнер видит старое содержимое. Почему?
    A)sed -i пишет новый файл и подменяет inode, а монтирование держит старый
    B)Файл в контейнере кэшируется слоем образа до перезапуска
    C)Монтирование файла делается копией, правки на хост не влияют
    D)sed -i меняет права, и контейнер теряет доступ к новой версии
    показать ответ и разбор
    +A)sed -i пишет новый файл и подменяет inode, а монтирование держит старый

    // разбор: Монтирование одиночного файла привязано к inode, а sed -i (как и большинство редакторов) создаёт временный файл и переименовывает его поверх — inode меняется. Контейнер продолжает смотреть на старый inode, и даже последующая запись в тот же путь до него не доходит. Лечится правкой на месте (запись в тот же inode) или монтированием каталога вместо файла плюс перезапуск контейнера.

  4. #dvo_text_tools4 / 5
    cut -f2 data.csv на CSV с запятыми возвращает всю строку целиком вместо второго поля. Почему?
    A)cut считает поля с нуля, так что поле 2 — это на деле третье
    B)cut по умолчанию режет по табу, а не по запятой — нужен -d','
    C)cut не понимает кодировку этого файла и потому берёт строку целиком
    D)Номер поля надо брать в кавычки: cut -f'2'
    показать ответ и разбор
    +B)cut по умолчанию режет по табу, а не по запятой — нужен -d','

    // разбор: У cut разделитель по умолчанию — табуляция; в строке без табов вся она считается одним полем, поэтому -f2 возвращает всё. Для CSV: cut -d','. Но и это ломается на запятых внутри кавычек — настоящий CSV парсят инструментом, понимающим кавычки (csvcut, python, awk с FPAT), а не наивным разбиением.

  5. #dvo_text_tools5 / 5
    В access.log поле $5 — размер ответа в байтах. Как одной командой посчитать суммарный отданный трафик?
    A)cat access.log | wc -c
    B)sort -n access.log | tail -1
    C)awk '{s+=$5} END{print s}' access.log
    D)grep -o '[0-9]\+' access.log | wc -l
    показать ответ и разбор
    +C)awk '{s+=$5} END{print s}' access.log

    // разбор: awk идеально для агрегатов по колонкам: s+=$5 копит сумму построчно, блок END печатает итог после обхода. Легко добавить фильтр (/pattern/{s+=$5}) или условие ($9==200). Для суммы по группам берут ассоциативный массив: s[$key]+=$5. wc/sort такого не умеют — они про строки и сортировку.

дальше

Теорию прочитали. Навык ставится повторением

В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.