LEARN X · ЗА 12 МИН

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}
Поддержать проект