如何在 cli 快速操作 json 資料 - jq

18 mins.
  1. 1. linux 安裝
  2. 2. 認識 filter
    1. 2.1. 根節點
    2. 2.2. 物件操作
    3. 2.3. 陣列操作
    4. 2.4. 資料運算
    5. 2.5. 修改內容
    6. 2.6. 讀取檔案
    7. 2.7. 傳入參數
  3. 3. 實際修改
  4. 4. Reference

想在 cli 的環境下要對 json 的檔案做修改,如果要用一行一行讀取後處理,實在是太麻煩而且太容易改錯,還好有一套工具 jq ,可以快速的操作整個 json 資料。
我把這個應用在 CI/CD 的環節中,在當下調整一些設定檔。

jq 到哪都是強者!!!

jq 可以被使用在任何一個有 cli 的 os 上 (Linux, OS X and Windows…etc.),選取的方法是透過 filter 來進行操作,先用個簡單的範例來示範,如何取得 foo 的值。

好像看到 $jq 的影子?

1
2
echo '{"foo": 42, "bar": "less interesting data"}' | jq '.foo'
> 42

linux 安裝

接下來將會起一個 Debian 的容器來做為示範,如何安裝並且操作。

不想安裝只是想嘗試的話可以用官方提供的線上工具 jq play

1
docker run --rm -it debian

進入到容器的環境內後,開始執行安裝,記得先 update 確保安裝的時候找的到套件

1
2
apt-get update
apt-get install -y jq

認識 filter

主要會專注在介紹最後實務上要用到的功能。

根節點

json 資料的格式都會有個最上層,也就是所謂的根結點,不管是陣列或是物件的形式,都是從最外層開始往內找。

1
2
3
4
5
echo '{"foo": 42, "bar": "less interesting data"}' | jq '.'
> {
> "foo": 42,
> "bar": "less interesting data"
> }

物件操作

1
2
3
4
5
6
7
8
9
10
# 單一屬性
echo '{"foo": 42, "bar": "less interesting data"}' | jq '.foo'
> 42
# 屬性不存在回 null
echo '{"notfoo": true, "alsonotfoo": false}' | jq '.foo'
> null
# 多個屬性
echo '{"foo": 42, "bar": "less interesting data"}' | jq '.["foo", "bbb"]'
> 42
> null

陣列操作

1
2
3
4
5
6
7
8
9
10
# 取得整個陣列並轉成 iterator
echo '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]' | jq '.[]'
> {"name":"JSON", "good":true}
> {"name":"XML", "good":false}
# 指定陣列位置
echo '[{"name":"JSON", "good":true}, {"name":"XML", "good":false}]' | jq '.[0]'
> {"name":"JSON", "good":true}
# 切割陣列 (也可用在字串中) [start:end]
echo '["a","b","c","d","e"]' | jq '.[2:4]'
> ["c", "d"]

資料運算

+- 可以用來運算數值或是陣列/字串的串接

1
2
3
4
5
6
7
8
9
10
11
12
13
# 運算數值
echo '{"foo": 42, "bar": "less interesting data"}' | jq '.foo + 3'
> 45
echo '{"foo": 42, "bar": "less interesting data"}' | jq '.foo - 2'
> 40
# 陣列串接
echo '["a","b","c","d","e"]' | jq '. + ["f","g"]'
> ["a","b","c","d","e","f","g"]
echo '["a","b","c","d","e"]' | jq '. - ["a"]'
> ["b","c","d","e"]
# 字串串接
echo '{"foo": 42, "bar": "less interesting data"}' | jq '.bar + " concat another string"'
> "less interesting data concat another string"

修改內容

1
2
echo '{"foo": 42, "bar": "less interesting data"}' | jq '.foo = 44'
> {"foo": 44, "bar": "less interesting data"}

讀取檔案

1
2
3
echo '{"foo": 42, "bar": "less interesting data"}' > file.json
jq '.foo' file.json
> 42

傳入參數

1
2
3
4
5
6
# 非 json 格式
echo '{"foo": 42, "bar": "less interesting data"}' | jq --arg concat " pass from arg" '.bar + $concat'
> "less interesting data pass from arg"
# json 格式
echo '["a","b","c","d","e"]' | jq --argjson concat '["f","g"]' '. + $concat'
> ["a","b","c","d","e","f","g"]

實際修改

實際上真正需要的是調整 db.json 這個檔案,先來看看資料格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"DBConfig": {
"DefaultDB": "LocalOrcl",
"EnableLog": true,
"DoNotCompleteFlag": false,
"DataBases": [
{
"DBName": "LocalOrcl",
"ConnectionString": "Data Source=localhost/ORCL;USER ID=jimmy;PASSWORD=jimmy;",
"Provider": "Oracle"
}
]
}
}

目標是要修改 DefaultDB 的值,並且增加一組連線資訊,基本上就是把前面所提到的 filter 都用過一次。

1
2
3
CONNECTION='{"DBName":"CI","ConnectionString":"Data Source=localhost/ORCL;USER ID=CI;PASSWORD=CI;","Provider":"Oracle"}'
jq '.DBConfig.DefaultDB = "CI"' db.json > tmp.$$.json && mv tmp.$$.json db.json
jq --arg CONNECTION "$CONNECTION" '.DBConfig.DataBases += $CONNECTION' db.json > tmp.$$.json && mv tmp.$$.json db.json

就可以看到 db.json 的內容被改成以下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"DBConfig": {
"DefaultDB": "CI",
"EnableLog": true,
"DoNotCompleteFlag": false,
"DataBases": [
{
"DBName": "LocalOrcl",
"ConnectionString": "Data Source=localhost/ORCL;USER ID=jimmy;PASSWORD=jimmy;",
"Provider": "Oracle"
},
{
"DBName": "CI",
"ConnectionString": "Data Source=localhost/ORCL;USER ID=CI;PASSWORD=CI;",
"Provider": "Oracle"
}
]
}
}

Reference