Фильтрация и выборка 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)— оставляет объекты, в которых возраст больше 18select(.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— передача переменной оболочки как строкового аргумента jqselectдля фильтрации по уровню журнала-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 — локальная установка не требуется.
Все уроки этого курса
- Фильтрация и выборка JSON с конвейерами jq
- Преобразование и создание объектов JSON с jq
- Использование REST API с curl и jq
- Редактирование файлов конфигурации YAML с yq