Когда переписывать хрупкий bash-конвейер на Python и как это сделать?
Этот однострочник считает строки ERROR по сервисам и вручную собирает JSON, но выдаёт хвостовую запятую и без фигурных скобок, не экранирует имена сервисов и молча даёт пустоту, когда grep не находит строк под pipefail.
Ограничения: вывод должен быть валидным JSON {"svc": count, …}; объясните признак, что bash-конвейеру пора стать Python-скриптом, затем перепишите его.
grep ERROR app.log | awk '{print $3}' | sort | uniq -c \
| awk '{printf "\"%s\": %s,", $2, $1}'
Скажите, когда предпочесть Python, и перепишите конвейер.
Переходите на Python, когда задача перерастает построчный текст — структурированный вывод (JSON), вложенные данные, обработка ошибок или тестируемая логика. Здесь самодельный JSON (хвостовая запятая, без экранирования) — верный признак. Перепишите: читайте лог, копите в dict/Counter и выводите через json.dumps — кавычки и структура тогда корректны.
- ✗Собирать JSON конкатенацией строк вместо
json.dumps - ✗Считать bash всегда верным выбором, потому что он короче в наборе
- ✗Игнорировать, что незакавыченные поля и голый
grepломаются подpipefail
- →Почему
json.dumpsбезопаснее сборки JSON-строки поле за полем? - →Как
grepбез совпадений взаимодействует сset -o pipefail?
Решение
Признак смены инструмента. bash хорош для построчных конвейеров, но здесь нужен структурированный вывод (JSON) с корректным экранированием, а под pipefail пустой grep роняет весь конвейер. Как только появляются структуры данных, экранирование, обработка ошибок или желание писать тесты — пора на Python.
#!/usr/bin/env python3
import json
from collections import Counter
counts = Counter()
with open("app.log") as f:
for line in f:
if "ERROR" not in line:
continue
parts = line.split()
if len(parts) >= 3:
counts[parts[2]] += 1 # сервис — третье поле
print(json.dumps(counts, ensure_ascii=False, sort_keys=True))
без хвостовой запятой.
покрыть unit-тестом.
Counterкопит счётчики за один проход — аналогsort | uniq -c, но в памяти.json.dumpsстроит валидный объект: скобки, кавычки и экранирование — за нас,- Пустой ввод даёт
{}, а не молчаливую пустоту; логикуsplit/фильтра легко
Это не значит «bash плох» — для grep | wc -l он идеален. Порог — структура и надёжность, а не размер файла.