0Pricing
DevOps Bootcamp · Урок

Фильтрация и выборка JSON с конвейерами jq

Переходите по вложенным объектам и массивам с помощью селекторов jq, каналов и фильтра select

«Фильтрация и выборка JSON с конвейерами jq» — бесплатный урок DevOps Bootcamp на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения DevOps Bootcamp, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс DevOps Bootcamp содержит 4 уроков всего.

Что такое jq и зачем он нужен

jq — это лёгкий и мощный инструмент командной строки для разбора, фильтрации и преобразования данных JSON. Это sed для JSON: Вы передаёте ему JSON через конвейер и получаете обратно структурированный вывод.

  • Предустановлен в большинстве дистрибутивов Linux или доступен через apt install jq / brew install jq
  • Без проблем работает в конвейерах оболочки вместе с curl, cat и другими инструментами
  • Поддерживает фильтрацию, отображение, свёртку и преобразование форматов

Базовый вызов выглядит так: jq '<filter>' file.json или с передачей через конвейер: cat file.json | jq '<filter>'. Фильтр . (точка) является тождественным фильтром и выводит весь документ в удобном для чтения виде.

# Pretty-print a JSON file
jq '.' data.json

# Or pipe from curl
curl -s https://api.github.com/users/torvalds | jq '.'

Выбор полей объекта с помощью точечной нотации

Чтобы обратиться к полю объекта JSON, используйте точечную нотацию: .fieldName. Для перехода по вложенным объектам селекторы можно объединять в цепочку.

  • .name — поле верхнего уровня
  • .address.city — вложенное поле
  • ."field-with-dash" — для полей со специальными символами нужны кавычки

Если поле не существует, jq возвращает null, а не сообщает об ошибке. Благодаря этому его безопасно использовать в скриптах без дополнительных проверок на null для необязательных полей.

# Given: {"name":"Alice","address":{"city":"Berlin","zip":"10115"}}
echo '{"name":"Alice","address":{"city":"Berlin","zip":"10115"}}' | jq '.name'
# Output: "Alice"

echo '{"name":"Alice","address":{"city":"Berlin","zip":"10115"}}' | jq '.address.city'
# Output: "Berlin"

Доступ к элементам массива и перебор

Доступ к массивам JSON осуществляется с помощью скобочной нотации. В jq индексация начинается с нуля.

  • .items[0] — первый элемент
  • .items[-1] — последний элемент
  • .items[1:3] — срез (от индекса 1 до индекса 3, не включая его)
  • .items[] — раскрывает массив: выводит каждый элемент как отдельное значение (это итератор)

Итератор [] — фундаментальная часть конвейеров jq: он позволяет независимо применять последующие фильтры к каждому элементу.

# Given an array of users
echo '[{"name":"Alice"},{"name":"Bob"},{"name":"Carol"}]' | jq '.[0]'
# Output: {"name":"Alice"}

# Iterate all elements and extract .name from each
echo '[{"name":"Alice"},{"name":"Bob"},{"name":"Carol"}]' | jq '.[].name'
# Output:
# "Alice"
# "Bob"
# "Carol"

Создание конвейеров jq с оператором конвейера

Как и конвейер оболочки |, jq имеет собственный внутренний оператор конвейера. Он передаёт вывод одного фильтра на вход следующему.

  • jq '.users[] | .name' — перебирает пользователей, а затем извлекает имя каждого
  • jq '.data | .items[] | .id' — переходит к данным, раскрывает массив items и извлекает идентификатор каждого элемента

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

Главная мысль: если итератор создаёт N значений, каждый последующий фильтр выполняется N раз — по одному разу для каждого значения.

# Nested pipeline: navigate -> iterate -> extract
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' \
  | jq '.users[] | .name'
# Output:
# "Alice"
# "Bob"

# Chain more stages
echo '{"users":[{"name":"Alice","age":30},{"name":"Bob","age":25}]}' \
  | jq '.users[] | .age'
# Output:
# 30
# 25

Фильтрация с помощью select()

Фильтр select(condition) пропускает значение только в том случае, если условие истинно; иначе он ничего не выводит. В jq это аналог grep или WHERE в SQL.

  • select(.age > 18) — оставляет объекты, в которых возраст больше 18
  • select(.status == "active") — проверка равенства
  • select(.name | startswith("A")) — проверка вложенной строки

Объединяйте select с итератором для фильтрации массивов: .items[] | select(.active) выводит только элементы, у которых .active имеет истинное значение.

# Filter array elements by a condition
echo '[{"name":"Alice","age":30},{"name":"Bob","age":17},{"name":"Carol","age":25}]' \
  | jq '.[] | select(.age >= 18) | .name'
# Output:
# "Alice"
# "Carol"

# Filter by string equality
echo '[{"name":"Alice","role":"admin"},{"name":"Bob","role":"user"}]' \
  | jq '.[] | select(.role == "admin") | .name'
# Output: "Alice"

Воссоздание объектов и массивов с помощью {} и []

jq позволяет изменять форму данных, создавая новые объекты с помощью {} и новые массивы с помощью [].

  • {name: .name, city: .address.city} — выбирает поля и переименовывает их в новом объекте
  • [.items[] | .id] — собирает перебранные значения обратно в массив
  • Сокращённая запись: {name, age} эквивалентна {name: .name, age: .age}

Заключение конвейера в [...] называется созданием массива и необходимо, когда на выходе нужен массив JSON, а не поток значений.

# Reshape: keep only selected fields
echo '[{"id":1,"name":"Alice","password":"secret"},{"id":2,"name":"Bob","password":"secret"}]' \
  | jq '[.[] | {id, name}]'
# Output:
# [
#   {"id": 1, "name": "Alice"},
#   {"id": 2, "name": "Bob"}
# ]

# Collect filtered names into an array
echo '[{"name":"Alice","active":true},{"name":"Bob","active":false}]' \
  | jq '[.[] | select(.active) | .name]'
# Output: ["Alice"]

Работа с вложенными массивами и рекурсивным спуском

В реальных данных JSON часто встречается глубокая вложенность. jq предоставляет два инструмента для навигации по глубоко вложенной структуре:

  • .a.b.c — явный путь, когда структура известна
  • .. | .fieldName? — рекурсивный спуск: обходит каждый узел дерева и выводит значения там, где существует данный ключ

Оператор ? (попытка) подавляет ошибки, если поле отсутствует в текущем узле. Это особенно важно при рекурсивном спуске по неоднородным деревьям.

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

# Explicit deep path
echo '{"a":{"b":{"c":42}}}' | jq '.a.b.c'
# Output: 42

# Recursive descent: find all "id" values anywhere in the tree
echo '{"users":[{"id":1,"profile":{"id":99}},{"id":2}]}' \
  | jq '.. | .id?'
# Output:
# 1
# 99
# 2

Практический пример: разбор ответов API curl

Один из самых распространённых вариантов применения jq — разбор ответов REST API, полученных с помощью curl. Сочетание curl -s (тихий режим) с конвейером jq позволяет извлекать чистые данные, удобные для обработки в скриптах.

  • Извлечь одно значение: curl -s URL | jq '.field'
  • Создать сводную таблицу: перебрать массив и воссоздать объекты, оставив только нужные поля
  • Использовать -r (необработанный вывод), чтобы убрать окружающие кавычки у строковых значений — это необходимо при присваивании значений переменным оболочки

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

#!/usr/bin/env bash
# Fetch GitHub repo info and extract specific fields
REPO="torvalds/linux"
RESPONSE=$(curl -s "https://api.github.com/repos/${REPO}")

# Extract fields
STARS=$(echo "$RESPONSE" | jq -r '.stargazers_count')
LANG=$(echo  "$RESPONSE" | jq -r '.language')
DESC=$(echo  "$RESPONSE" | jq -r '.description')

echo "Stars : $STARS"
echo "Lang  : $LANG"
echo "Desc  : $DESC"

Использование map() и map_values()

jq предоставляет две удобные функции высшего порядка для преобразования коллекций:

  • map(f) — применяет фильтр f к каждому элементу массива и возвращает новый массив. Эквивалент: [.[] | f].
  • map_values(f) — применяет f к каждому значению объекта или массива, сохраняя ключи и индексы.

Эти функции читаются лучше, чем ручное заключение конвейеров в [], и соответствуют идиоматическому стилю jq для преобразований, результат которых должен оставаться массивом.

# map: extract a field from each element
echo '[{"name":"Alice","score":95},{"name":"Bob","score":80}]' \
  | jq 'map(.name)'
# Output: ["Alice", "Bob"]

# map with select: filter + transform in one step
echo '[{"name":"Alice","score":95},{"name":"Bob","score":60}]' \
  | jq 'map(select(.score >= 70) | .name)'
# Output: ["Alice"]

# map_values: multiply every value in an object by 2
echo '{"a":1,"b":2,"c":3}' | jq 'map_values(. * 2)'
# Output: {"a":2,"b":4,"c":6}

Обработка необязательных полей и значений по умолчанию с помощью //

Данные JSON из внешних источников часто бывают непоследовательными: поля могут отсутствовать или иметь значение null. jq предоставляет оператор альтернативы // (двойной слеш) для задания значения по умолчанию.

  • .nickname // "anonymous" — использует .nickname, если оно не равно null и не является ложным, иначе использует "anonymous"
  • .count // 0 — числовое значение по умолчанию
  • В сочетании с select: select((.status // "inactive") == "active")

Это значительно короче эквивалента в оболочке ${VAR:-default} и удобно встраивается в более длинные конвейеры.

# Provide defaults for missing/null fields
echo '[{"name":"Alice","role":"admin"},{"name":"Bob"}]' \
  | jq '[.[] | {name, role: (.role // "user")}]'
# Output:
# [
#   {"name": "Alice", "role": "admin"},
#   {"name": "Bob",   "role": "user"}
# ]

# Numeric default
echo '{"items":[1,2,3]}' | jq '.total // 0'
# Output: 0

Практический скрипт: анализатор журнала JSON

Структурированное журналирование в формате JSON является стандартом современных систем. Ниже приведён реалистичный скрипт, который читает файл журнала в формате JSON с одним объектом в каждой строке, отбирает записи об ошибках и форматирует удобную для чтения сводку.

Основные использованные шаблоны:

  • -c (компактный вывод) — один объект JSON в строке, удобно для передачи в циклы оболочки
  • --arg name value — передача переменной оболочки как строкового аргумента jq
  • select для фильтрации по уровню журнала
  • -r для вывода необработанных строк, подходящего для echo
#!/usr/bin/env bash
# Parse newline-delimited JSON logs and report ERRORs
# Each log line: {"level":"ERROR","msg":"...","ts":"2024-01-15T10:23:00Z","svc":"auth"}

LOG_FILE="/var/log/app/app.log"
LEVEL="ERROR"

echo "=== $LEVEL entries in $LOG_FILE ==="

jq -r --arg lvl "$LEVEL" \
  'select(.level == $lvl) | "[\(.ts)] [\(.svc)] \(.msg)"' \
  "$LOG_FILE"

# Count errors per service
echo ""
echo "=== Error count by service ==="
jq -r --arg lvl "$LEVEL" \
  'select(.level == $lvl) | .svc' "$LOG_FILE" \
  | sort | uniq -c | sort -rn

Проверка знаний: поведение select() в jq-конвейере

Проверьте, насколько Вы понимаете работу select() внутри конвейера jq.

Дана следующая команда:

echo '[{"name":"Alice","age":30},{"name":"Bob","age":17},{"name":"Carol","age":22}]' | jq '[.[] | select(.age >= 18) | .name]'

Каким будет результат?

Итоги урока: конвейеры jq для фильтрации JSON

Вы изучили основные инструменты jq для навигации по JSON и его фильтрации в командной строке:

  • Точечная нотация (.field, .a.b.c) выбирает поля объектов
  • Доступ к массивам (.[0], .[]) обращается к элементам массивов по индексу и перебирает их
  • Оператор конвейера (|) соединяет фильтры; каждый этап обрабатывает все значения предыдущего этапа
  • select(cond) фильтрует значения, пропуская только те, для которых условие истинно
  • Создание объектов и массивов ({}, [], map()) преобразует данные в новые структуры
  • Оператор альтернативы (//) подставляет значения по умолчанию для отсутствующих полей или полей со значением null
  • Флаг -r убирает кавычки при присваивании значения переменной оболочки; --arg безопасно передаёт переменные оболочки
  • Рекурсивный обход (.. | .field?) выполняет поиск в глубоко вложенных деревьях, когда путь неизвестен

С помощью этих строительных блоков Вы можете преобразовать любой ответ JSON API, файл журнала или конфигурацию ровно в те данные, которые нужны Вашим скриптам, — и всё это, не покидая терминал.

Часто задаваемые вопросы

Урок «Фильтрация и выборка JSON с конвейерами jq» бесплатный?

Да — полный текст урока «Фильтрация и выборка JSON с конвейерами jq» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс DevOps Bootcamp, подпишись на CoddyKit PRO. Курс DevOps Bootcamp содержит 4 уроков всего.

Чему я научусь в уроке «Фильтрация и выборка JSON с конвейерами jq»?

Переходите по вложенным объектам и массивам с помощью селекторов jq, каналов и фильтра select Ты практикуешь DevOps Bootcamp с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать DevOps Bootcamp?

Предыдущий опыт не требуется. DevOps Bootcamp на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Фильтрация и выборка JSON с конвейерами jq»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке DevOps Bootcamp?

Да. Каждый урок DevOps Bootcamp включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Фильтрация и выборка JSON с конвейерами jq
  2. Преобразование и создание объектов JSON с jq
  3. Использование REST API с curl и jq
  4. Редактирование файлов конфигурации YAML с yq
← Назад к DevOps Bootcamp