我們假設你已經熟悉 JSON 的語法和資料型態。
jq 的運作方式,是把傳入的 JSON 資料通過_單一運算式_(寫成_過濾器管線_),以得到想要的轉換後資料。
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選項。
你會在練習題的測試指令碼中看到這種用法。
這堂課接下來的內容會聚焦在jq_語言_本身。
過濾器也稱為運算式。
一個_過濾器_接受輸入並產生輸出。
就像你在 unix shell 裡工作的方式一樣,你可以用管線|串接_過濾器_,把一個_過濾器_的輸出接到另一個的輸入。
.
這是最簡單的_過濾器_。 它只是把輸入直接傳到輸出。
$ 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 '[{"key":"question", "value":54}, {"key":"answer", "value":42}]' \
| jq '{(.[0].key): .[0].value, (.[1].key): .[1].value}'
舉例來說,假設file.json的內容是
{
"key1": "value1",
"key2": [5, 15, 25]
}
讓我們來算算 key2 _陣列_的長度:
$ jq '.key2 | length' file.json
3
我們把.key2運算式的輸出_用管線接到_length的輸入,毫不意外地,它會輸出_陣列_中的元素個數。
在這個範例中,輸入的 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
]
這裡不深入探討(函式會是另一個練習的主題),以下是一些實用的內建函式:
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) ]
註解以#字元開頭,一直持續到該行的結尾。
你突然很想吃鬆餅。 身為一個現代、喜愛科技的人,你用 JSON 格式寫了一份鬆餅材料的購物清單。 清單的結構是:
{
"name": "name of shopping list",
"ingredients": [ ...list of ingredients... ],
"optional ingredients": [ ...list of ingredients... ]
}
一份材料會用 JSON 物件表示,像這樣:
{
"item": "flour",
"amount": {
"quantity": 2,
"unit": "cup"
}
}
每份材料都有一個名為 "substitute" 的選用屬性,裡面是一個字串,代表可以拿來替代的另一項材料。
來看看我們怎麼用 jq 檢查這份購物清單吧。
寫一個運算式,輸出購物清單的 "name" 元素。
只計算「必要」的材料。不要包含選用的材料。
寫一個運算式,輸出糖的份量。 只要數字的部分。
有些材料可以替換(如果你沒有材料 X,可以用 Y 代替)。 輸出一個 JSON 物件,將建議的材料對應到它的替代品。 在對應中包含選用的材料。