基礎

基礎 在 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

    從filename讀取jq程式,而不是在命令列上直接提供。 sed和awk也基於同樣的目的使用-f選項。 你會在練習題的測試腳本裡看到這種用法。

關於所有選項的詳細說明,請參閱手冊。

過濾器與管線

過濾器也稱為運算式。

一個_過濾器_接受輸入並產生輸出。 就像你在 Unix shell 裡工作的方式,你可以用管線|把_過濾器_串接起來,將一個_過濾器_的輸出接到另一個_過濾器_的輸入。

恆等過濾器:.

這是最簡單的_過濾器_。 它就只是把輸入原封不動地傳到輸出。 例如,jq預設會美化輸出,所以把 JSON 送進.過濾器,就能免費得到排得漂漂亮亮的輸出!

$ echo '[1, 2, 3]' | jq '.'
[
  1,
  2,
  3
]

陣列

這裡會快速介紹如何使用陣列。 之後我們會更詳細地探討這個主題。

_陣列_元素用方括號存取,索引從 0 開始。

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

方括號也可以用在_物件_上,但這時字串字面值就需要加上引號。 這是處理含有空白的鍵的其中一種方法。

$ 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 相同:數字、字串、布林值、陣列、物件(用 JSON 的說法,就是只有字串鍵的雜湊),以及 "null"。

之後的練習會讓你更了解這些。

空白

空白在jq裡不具意義。 你可以自由使用空格、定位點或換行來編排你的程式碼。 我們並不知道有任何現成的jq風格指南。

不可變的值

jq裡的值是_不可變的_。 修改值的過濾器會輸出一個新的值。 這表示jq沒有全域變數,你需要習慣把狀態從一個過濾器傳到另一個過濾器。

「真假值」

false和null這兩個值會被視為 false。其他任何值 (包括數字零,以及空字串、空陣列、空物件)都是 true。

函式與運算子

我們不深入探討(函式會是另一個練習的主題),這裡先列出幾個好用的內建函式:

  • 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

    給定_某個_輸入和一個過濾器作為引數:

    • 如果套用到引數的過濾器結果為 true,就原封不動輸出輸入
    • 否則就輸出_什麼都沒有_(不是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 編輯 連結會在新視窗或分頁中開啟

學習 基礎