Ос

Основи у jq

1 вправа

Про концепцію Основи

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]}'
Note

Досить часто виникає потреба витягти підмножину ключів із великого обʼєкта. Наприклад, щоб витягти 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, яка, що не дивно, видає кількість елементів у масиві.

Caution

Це та особливість 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 вважаються неправдою. Будь-яке інше значення (зокрема число нуль і порожній рядок тексту, масив чи обʼєкт) вважається правдою.

Функції та оператори

Не заглиблюючись надто (функції будуть темою іншої вправи), ось кілька корисних вбудованих функцій:

  • length

    Отримуючи масив як вхідні дані, видає кількість елементів у масиві.

    $ 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.

  • map

    Отримуючи масив як вхідні дані та фільтр як аргумент, видає масив, у якому фільтр застосовано до кожного елемента

    $ jq -c -n '[10, 20, 30, 40] | map(. / 5)'
    [2,4,6,8]
    
  • select

    Отримуючи якісь вхідні дані та фільтр як аргумент:

    • якщо фільтр, застосований до аргументу, дає значення правда, видає вхідні дані без змін
    • інакше не видає нічого (не значення 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) ]
      

Коментарі

Коментарі починаються із символу # і тривають до кінця рядка.

Редагувати через GitHub Посилання відкривається в новому вікні або вкладці

Вивчити концепцію Основи