jq
jq за 12 минут: фильтры, конвейеры, map/select, конструирование объектов, sort_by/group_by, строки и условия для обработки JSON в терминале.
jq — это процессор JSON для командной строки. Он принимает JSON на вход, прогоняет его через фильтр и выдаёт JSON (или текст) на выход. Незаменим для разбора ответов API, логов и конфигов прямо в терминале. Весь язык jq укладывается в одну страницу плотно прокомментированных примеров.
Что такое jq и базовый вызов
jq читает JSON со стандартного ввода (или из файла) и применяет к нему фильтр. По умолчанию вывод подсвечивается и форматируется с отступами.
# Передаём JSON через pipe и применяем фильтр '.'
echo '{"name": "Аня", "age": 25}' | jq '.'
# => { "name": "Аня", "age": 25 } (красиво отформатировано)
# Читаем из файла вместо stdin
jq '.' data.json
# Полезные флаги:
jq -r '.name' data.json # -r (raw) — строки без кавычек
jq -c '.' data.json # -c (compact) — компактный вывод в одну строку
jq -n '1 + 2' # -n (null) — запуск без входных данных, => 3
echo '{}' | jq '.a // "нет"' # фильтр — это выражение, а не команда
Идентичность и доступ к полям
Точка . — это сам вход. Из неё «вытаскивают» поля через .key, вложенность пишут цепочкой.
echo '{"user": {"name": "Аня", "city": "Москва"}}' | jq '.' # весь объект целиком
echo '{"name": "Аня"}' | jq '.name' # => "Аня"
echo '{"user": {"name": "Аня"}}' | jq '.user.name' # вложенность => "Аня"
# Ключ с пробелами/спецсимволами — в кавычках и квадратных скобках
echo '{"first name": "Аня"}' | jq '.["first name"]' # => "Аня"
# Опциональный доступ: ? не падает, если поля нет
echo '{"name": "Аня"}' | jq '.email?' # => null (без ошибки)
# Несколько полей сразу — через запятую (даёт несколько результатов)
echo '{"a": 1, "b": 2}' | jq '.a, .b' # => 1 затем 2
Массивы: элементы, индексы, срезы
.[] «разворачивает» массив в поток отдельных значений. Индексы и срезы — как в Python.
echo '[10, 20, 30, 40]' | jq '.[]' # развернуть => 10 20 30 40 (по строкам)
echo '[10, 20, 30, 40]' | jq '.[0]' # первый элемент => 10
echo '[10, 20, 30, 40]' | jq '.[-1]' # последний => 40
echo '[10, 20, 30, 40]' | jq '.[1:3]' # срез [1,3) => [20, 30]
echo '[10, 20, 30, 40]' | jq '.[:2]' # первые два => [10, 20]
# Поле внутри каждого элемента массива
echo '[{"n": 1}, {"n": 2}]' | jq '.[].n' # => 1 затем 2
# Собрать обратно в массив: обернуть в [ ... ]
echo '[{"n": 1}, {"n": 2}]' | jq '[.[].n]' # => [1, 2]
Конвейер: оператор |
Внутри jq свой | — он передаёт результат левого фильтра на вход правому, как pipe в shell.
# Достать массив users, развернуть, взять поле name у каждого
echo '{"users": [{"name": "Аня"}, {"name": "Боб"}]}' \
| jq '.users | .[] | .name' # => "Аня" затем "Боб"
# То же короче: цепочка склеивается
echo '{"users": [{"name": "Аня"}]}' | jq '.users[].name' # => "Аня"
# Левая часть готовит данные, правая — обрабатывает
echo '[3, 1, 2]' | jq 'sort | .[0]' # отсортировать, взять первый => 1
Функции и трансформации: map, select, length, keys
Это рабочая лошадка jq: map применяет фильтр к каждому элементу, select оставляет подходящие.
echo '[1, 2, 3]' | jq 'map(. * 2)' # к каждому => [2, 4, 6]
echo '[1, 2, 3, 4]' | jq 'length' # длина массива => 4
echo '{"a": 1, "b": 2}' | jq 'length' # число ключей => 2
echo '"привет"' | jq 'length' # длина строки => 6
echo '{"a": 1, "b": 2}' | jq 'keys' # ключи (отсортированы) => ["a", "b"]
echo '{"a": 1, "b": 2}' | jq 'values' # значения => [1, 2]
echo '{"a": 1}' | jq 'has("a")' # есть ли ключ => true
echo '[1, 2, 3]' | jq 'has(1)' # есть ли индекс => true
# map(select(...)) — отфильтровать элементы массива
echo '[1, 2, 3, 4]' | jq 'map(select(. % 2 == 0))' # чётные => [2, 4]
Фильтрация через select
select(условие) пропускает значение дальше, только если условие истинно, иначе «глотает» его.
# Оставить пользователей старше 18
echo '[{"name": "Аня", "age": 25}, {"name": "Ким", "age": 15}]' \
| jq '.[] | select(.age > 18)' # => {"name": "Аня", "age": 25}
# Сравнения и логика: == != > < >= <= and or not
echo '[1, 2, 3, 4, 5]' | jq '.[] | select(. >= 2 and . <= 4)' # => 2 3 4
# По строковому полю
echo '[{"role": "admin"}, {"role": "user"}]' \
| jq '.[] | select(.role == "admin")' # => {"role": "admin"}
# Проверка вхождения подстроки
echo '["apple", "banana"]' | jq '.[] | select(contains("an"))' # => "banana"
Конструирование объектов и массивов
В { ... } и [ ... ] можно собирать новые структуры из кусков входа.
# Новый объект: ключ — литерал, значение — фильтр
echo '{"name": "Аня", "age": 25, "city": "Москва"}' \
| jq '{имя: .name, возраст: .age}' # => {"имя": "Аня", "возраст": 25}
# Сокращение: {name} означает {name: .name}
echo '{"name": "Аня", "age": 25}' | jq '{name, age}' # => {"name":"Аня","age":25}
# Вычисляемое значение и переименование
echo '{"price": 100, "qty": 3}' | jq '{total: (.price * .qty)}' # => {"total": 300}
# Массив из выбранных полей по каждому элементу
echo '[{"name": "Аня", "age": 25}, {"name": "Боб", "age": 30}]' \
| jq 'map({name})' # => [{"name":"Аня"}, {"name":"Боб"}]
Встроенные функции: sort_by, group_by, unique, add
Готовые агрегаторы избавляют от ручных циклов.
echo '[3, 1, 2]' | jq 'sort' # => [1, 2, 3]
echo '[3, 1, 2]' | jq 'min' # минимум => 1
echo '[3, 1, 2]' | jq 'max' # максимум => 3
echo '[1, 2, 3, 4]' | jq 'add' # сумма => 10
echo '[1, 1, 2, 3, 3]' | jq 'unique' # уникальные => [1, 2, 3]
echo '[3, 1, 2]' | jq 'reverse' # развернуть => [2, 1, 3]
# Сортировка объектов по полю
echo '[{"n": "Боб", "age": 30}, {"n": "Аня", "age": 25}]' \
| jq 'sort_by(.age)' # => сначала Аня (25), потом Боб (30)
# Группировка по полю (даёт массив массивов)
echo '[{"city": "МСК"}, {"city": "СПб"}, {"city": "МСК"}]' \
| jq 'group_by(.city)' # => [[{МСК},{МСК}], [{СПб}]]
# Среднее: сумма поделить на длину
echo '[10, 20, 30]' | jq 'add / length' # => 20
Строки: split, join, регистр, @csv
jq умеет интерполяцию \(...) и форматы вывода вроде @csv, @tsv, @json.
echo '"a,b,c"' | jq 'split(",")' # => ["a", "b", "c"]
echo '["a", "b", "c"]' | jq 'join("-")' # => "a-b-c"
echo '"Привет"' | jq 'ascii_downcase' # => "привет" (только ASCII)
echo '"HELLO"' | jq 'ascii_downcase' # => "hello"
echo '" hi "' | jq 'ltrimstr(" ")' # убрать префикс
echo '"hello"' | jq 'test("^h")' # regex-проверка => true
# Интерполяция строк через \(выражение)
echo '{"name": "Аня"}' | jq -r '"Привет, \(.name)!"' # => Привет, Аня!
# Вывод строки в CSV (требует -r, на вход — массив)
echo '["Аня", 25, "Москва"]' | jq -r '@csv' # => "Аня",25,"Москва"
echo '["a", "b"]' | jq -r '@tsv' # => a<TAB>b (поля через табы)
Условия: if-then-else и оператор //
Полноценный if и удобный // для значений по умолчанию.
# if-then-else-end (else обязателен, если нужен иной результат)
echo '18' | jq 'if . >= 18 then "взрослый" else "ребёнок" end' # => "взрослый"
# elif для нескольких веток
echo '75' | jq 'if . >= 90 then "A" elif . >= 60 then "B" else "F" end' # => "B"
# Оператор // — «или по умолчанию»: берёт правое, если левое null/false/ошибка
echo '{"name": "Аня"}' | jq '.nickname // .name' # => "Аня"
echo '{}' | jq '.count // 0' # => 0
# Внутри map для подстановки заглушек
echo '[{"email": "[email protected]"}, {}]' \
| jq 'map(.email // "нет почты")' # => ["[email protected]", "нет почты"]
Практические примеры
Собираем выученное на типичных задачах с реальными API-ответами.
# 1) Извлечь имена репозиториев из ответа GitHub API
curl -s https://api.github.com/users/torvalds/repos \
| jq -r '.[].name' # -r и .name по каждому элементу => список имён
# 2) Посчитать, сколько элементов удовлетворяет условию
echo '[{"ok": true}, {"ok": false}, {"ok": true}]' \
| jq '[.[] | select(.ok)] | length' # => 2
# 3) Превратить массив объектов в CSV-таблицу
echo '[{"name": "Аня", "age": 25}, {"name": "Боб", "age": 30}]' \
| jq -r '.[] | [.name, .age] | @csv'
# => "Аня",25
# => "Боб",30
# 4) Сумма поля по всем элементам
echo '[{"sum": 100}, {"sum": 250}]' | jq '[.[].sum] | add' # => 350
# 5) Топ-3 самых дорогих товара (отсортировать, развернуть, срезать)
echo '[{"n": "A", "p": 5}, {"n": "B", "p": 9}, {"n": "C", "p": 7}]' \
| jq 'sort_by(.p) | reverse | .[0:3] | map(.n)' # => ["B", "C", "A"]
# 6) Плоский объект «ключ -> значение» из массива пар
echo '[{"k": "a", "v": 1}, {"k": "b", "v": 2}]' \
| jq 'map({(.k): .v}) | add' # => {"a": 1, "b": 2}