jq працює так: вхідні дані JSON проходять через один вираз (записаний як конвеєр фільтрів), і на виході ми отримуємо потрібні перетворені дані.
jq
Мову jq реалізує програма jq.
Ця програма має кілька зручних параметрів командного рядка, які керують тим, як споживаються вхідні дані та як подаються вихідні.
Нижче в прикладах ми побачимо:
-n або --null-input
Зазвичай програмі jq передають файл для читання або надсилають дані на її вхід.
Параметр --null-input дає змогу згенерувати дані JSON без жодних вхідних даних.
-c або --compact-output
jq типово гарно форматує свої вихідні дані.
Людям надзвичайно зручно дивитися на дані, коли вони гарно відформатовані.
Однак машинам це не потрібно: параметр --compact-output прибирає пробільні символи форматування, щоб зменшити розмір отриманого JSON.
-f filename або --from-file filename
Читає програму jq з файлу filename замість того, щоб задавати її в командному рядку.
І sed, і awk використовують параметр -f для тієї самої мети.
Цей параметр використовується в тестових скриптах практичних вправ.
Докладніше про всі параметри можна прочитати в посібнику.
Фільтри також називають виразами.
Фільтр приймає вхідні дані й видає вихідні.
Як і в оболонці Unix, фільтри можна зʼєднувати конвеєром |, щоб передавати вихідні дані одного фільтра на вхід іншого.
.
Це найпростіший фільтр.
Він просто передає свої вхідні дані на вихід.
Наприклад, jq типово форматує вихідні дані, тож якщо пропустити JSON через фільтр ., ми безкоштовно отримаємо гарно відформатовані вихідні дані!
$ echo '[1, 2, 3]' | jq '.'
[
1,
2,
3
]
Це буде короткий вступ до роботи з масивами. Докладніше ми розглянемо цю тему пізніше.
До елементів масиву звертаються через дужки, а індексація починається з нуля.
$ echo '[10, 20, 30]' | jq '.[1]'
20
Фільтр може побудувати масив, узявши вираз у [ та ]
зі сталим переліком елементів:
jq -n '[1, 2, 3]'
щоб зібрати потік елементів: наприклад,
range - це функція, яка видає потік чисел
$ jq -n 'range(10; 70; 15)'
10
25
40
55
Використання [] збирає результати виразу в масив
$ jq -c -n '[range(10; 70; 15)]'
[10,25,40,55]
Кома - це не просто синтаксис, що розділяє елементи масиву. Кома - це оператор, який обʼєднує потоки.
Наприклад, [1, 2, 3] - це фільтр, який використовує конструктор масиву [], щоб зібрати результат обʼєднання трьох виразів 1, 2 і 3.
Звернімо увагу на крапки з комою в range(10; 70; 15) вище?
Оскільки коми в мові jq мають особливе призначення, функції, які приймають кілька аргументів, розділяють аргументи крапками з комою.
Короткий вступ до обʼєктів.
Як і в багатьох мовах програмування, до властивостей обʼєкта звертаються через крапку
$ echo '{"foo": {"bar": "qux"}}' | jq '.foo.bar'
"qux"
Для обʼєктів можна використовувати й дужки, але тоді рядкові літерали (англ. string) потребують лапок. Це один зі способів працювати з ключами, що містять пробіли.
$ echo '{"foo bar": "qux"}' | jq '.["foo bar"]'
"qux"
Обʼєкт можна побудувати з {} та пар key: value.
Лапки не потрібні навколо ключів, які є «простими» рядками тексту.
jq -n '{question: (6 * 9), answer: 42}'
видає
{
"question": 54,
"answer": 42
}
Щоб ключ було оброблено як вираз, його треба взяти в круглі дужки (наступний приклад видає те саме, що й попередній).
echo '[["question", "answer"], [54, 42]]' \
| jq '{(.[0][0]): .[1][0], (.[0][1]): .[1][1]}'
Досить часто виникає потреба витягти підмножину ключів із великого обʼєкта.
Наприклад, щоб витягти id і name з
{
"id": 101,
"name": "alpha widget",
"specifications": {...}
}
можна написати
{id: .id, name: .name}
Але це настільки часта потреба, що для неї є скорочений синтаксис:
{id, name}
Наприклад, якщо файл file.json містить
{
"key1": "value1",
"key2": [5, 15, 25]
}
Обчислімо довжину масиву key2:
$ jq '.key2 | length' file.json
3
Ми спрямовуємо вихідні дані виразу .key2 на вхід length, яка, що не дивно, видає кількість елементів у масиві.
Це та особливість jq, до якої треба звикнути:
більшість (але не всі) функцій поводяться як фільтри, тобто дані передають на вхід фільтра, а не як аргумент.
У цьому прикладі вхідні дані JSON ігноруються й не впливають на вихідні дані:
$ echo '{"answer": 42}' | jq '6 * 9'
54
Фільтр може видати більше ніж одне значення.
Наприклад, фільтр .[] видає кожен елемент масиву як окреме значення:
$ jq -n -c '[1, 2, 3]'
[1,2,3]
$ jq -n -c '[1, 2, 3] | .[]'
1
2
3
Якщо спрямувати такий фільтр в інший, то другий фільтр буде виконуватися для кожного значення:
$ jq -n -c '[1, 2, 3] | .[] | . * 2'
2
4
6
Це схоже на неявну ітерацію.
Коли ми зрозуміємо цю техніку, то побачимо, що дуже потужні фільтри jq можуть бути зовсім короткими.
Круглі дужки обʼєднують підвирази в групи, щоб задати порядок обчислень, як і в інших мовах.
У jq потреба в них може видатися дещо несподіваною.
Наприклад, припустімо, ми хочемо побудувати масив з 2 елементів: квадратний корінь з 9 та e у степені 1.
Окремі вирази такі: 9 | sqrt та 1 | exp.
Ми сподіваємося побачити на виході масив [3, 2.7...]
$ jq -n '[ 9|sqrt, 1|exp ]'
[
20.085536923187668,
2.718281828459045
]
Чому ми не отримали те, що сподівалися? jq розуміє це так:
[ ((9|sqrt), 1) | exp ]
jq будує потік із двох елементів (3 і 1), і кожен з них потрапляє до exp.
Треба подбати, щоб exp приймав лише одне число як вхідні дані.
Інакше кажучи, треба зробити так, щоб конвеєр обчислювався раніше за кому.
$ jq -n '[ 9|sqrt, (1|exp) ]'
[
3,
2.718281828459045
]
jq підтримує той самий набір типів даних, що й JSON: числа, рядки тексту, булеві значення (англ. Boolean), масиви, обʼєкти (які мовою JSON є хешами, де ключами можуть бути лише рядки тексту), та «null».
Більше про них ми дізнаємося в наступних вправах.
Пробільні символи в jq не мають значення.
Ми можемо використовувати пробіли, табуляції та перенесення рядків так, як нам зручно форматувати код.
Нам невідомі жодні наявні посібники зі стилю для jq.
Значення в jq незмінні.
Фільтри, які змінюють значення, видадуть нове значення.
Це означає, що в jq немає глобальних змінних, тож нам доведеться звикнути передавати стан від одного фільтра до іншого.
Значення false і null вважаються неправдою. Будь-яке інше значення
(зокрема число нуль і порожній рядок тексту, масив чи обʼєкт) вважається правдою.
Не заглиблюючись надто (функції будуть темою іншої вправи), ось кілька корисних вбудованих функцій:
Отримуючи масив як вхідні дані, видає кількість елементів у масиві.
$ jq -n '[10, 20, 30, 40] | length'
4
Цей оператор робить різні речі залежно від типу своїх операндів: додає числа, зʼєднує рядки тексту, приєднує масиви, обʼєднує обʼєкти.
$ jq -c -n '
3 + 4,
"foo" + "bar",
["a", "b"] + ["c"],
{"m": 10} + {"n": 20}
'
7
"foobar"
["a","b","c"]
{"m":10,"n":20}
add - це функція, яка приймає масив і повертає елемент, у якому всі елементи додано разом за правилами +.
[1, 2, 3] | add видає 6.
Отримуючи масив як вхідні дані та фільтр як аргумент, видає масив, у якому фільтр застосовано до кожного елемента
$ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
[2,4,6,8]
Отримуючи якісь вхідні дані та фільтр як аргумент:
null, а справді жодних вихідних даних)Наприклад, маючи деякі числа, виберімо ті, що діляться на 3
$ jq -n 'range(10) | select(. % 3 == 0)'
0
3
6
9
Пригадаймо, що range видає потік чисел.
select викликатиметься один раз для кожного числа.
Виводяться лише ті числа, які «проходять» вираз.
Часто потрібно вибрати елементи масиву. Є кілька способів це зробити.
Маючи вхідні дані ["Anne", "Bob", "Cathy", "Dave"], виберімо імена довжиною 4.
використаймо map і select разом
map(select(length == 4))
розкладімо масив на елементи, застосуймо select до цього потоку й зберімо результати
[ .[] | select(length == 4) ]
Коментарі починаються із символу # і тривають до кінця рядка.