[Linux] jq 指令:好用又輕量的 JSON 工具

jq 簡介

jq 是一個常用的指令工具,用於處理 JSON 格式的資料,輕量但功能強大,可以做到從 JSON 資料中擷取、轉換、篩選等操作,也可以簡單用於排版讓 JSON 資料更可讀。

這邊文章筆記常用的 jq 功能和範例。

安裝 jq

macOS (by homebrew)

brew list jq

Linux

## for Ubuntu/Debian:
sudo apt-get update
sudo apt-get install jq

## for CentOS/Red Hat:
sudo yum install jq

排版

範例資料 example1.json

❯ $ cat example1.json
{"name":"John",
"age":18}

增加人眼可讀性 – pretty-printed

$ cat example1.json | jq   # 或 `cat example1.json | jq .` 亦可
{
  "name": "John",
  "age": 18
}

壓縮資料 – compact

濾掉多餘的換行、空白:

$ cat example1.json | jq -c
{"name":"John","age":18}

序列化/反序列化(Serialization/Deserialization)

不同作業系統或程式語言,可能使用不同的內部表示方式,為了解決資料互通問題,所以需要序列化。序列化(Serialization)是將資料結構或物件,轉換成可被解讀的格式,可以存儲在檔案中或者在網絡上傳輸,在不同系統或環境之間互通。

JSON 序列化後會是一段字串,jq 中也提供了 2 個跟序列化/反序列化有關的參數:

Option Description
-R 序列化 (將 JSON 格式轉成可以傳輸的字串)
-r 反序列化 (將字串轉回 JSON 格式)

序列化 (將 JSON 格式轉成可以傳輸的字串)

## 範例資料
❯ $ cat example1.json
{"name":"John",
"age":18}

## 序列化用法
❯ $ cat example1.json | jq -R
"{\"name\":\"John\","
"\"age\":18}"

## 小技巧:如果想要緊實美觀一點,可以先壓縮再序列化
❯ $ cat example1.json | jq -c | jq -R
"{\"name\":\"John\",\"age\":18}"

反序列化 (將字串轉回 JSON 格式)

## 序列化過的範例資料
❯ $ cat example_serialization.txt
"{\"name\":\"John\",\"age\":18}"

## 如果沒有先反序列化,直接解析沒用,只會被當普通字串
❯ $ cat example_serialization.txt | jq
"{\"name\":\"John\",\"age\":18}"

## 反序列化用法
❯ $ cat example_serialization.txt | jq -r
{"name":"John","age":18}

## 小技巧:反序列化再排版
❯ $ cat example_serialization.txt | jq -r | jq
{
  "name": "John",
  "age": 18
}

取出特定欄位的值

範例資料 example2.json

❯ $ cat example2.json
{"name": "John",
"age": 18,
"job": null,
"skills": ["Java", "C#", "Node"],
"address":{"country": "Taiwan", "city": "Taipie", "district": "Xinyi"}}

取得某一個 key 的值

兩種寫法都可以:

❯ $ cat example2.json | jq .name
❯ $ cat example2.json | jq '.["name"]'
"John"

如果 key 不存在,會回 null:

❯ $ cat example2.json | jq .aaa
null

如果 key 存在但 value 是 null,也是回傳 null:

❯ $ cat example2.json | jq .job
null

取得多個 key 的值

❯ $ cat example2.json | jq '.name, .age, .skills, .aaa'
❯ $ cat example2.json | jq '.["name", "age", "skills", "aaa"]'

"John"
18
[
  "Java",
  "C#",
  "Node"
]
null

可以把結果包成一個陣列,方便後續繼續做更多處理:

❯ $ cat example2.json | jq '[.["name", "age", "skills", "aaa"]]'
[
  "John",
  18,
  [
    "Java",
    "C#",
    "Node"
  ],
  null
]

取出第二層以上的欄位值

這兩種寫法都可以:

❯ $ cat example2.json | jq .address.city
❯ $ cat example2.json | jq '.["address"]["city"]'
"Taipie"

小技巧:取出字串值並清理雙引號

❯ $ cat example2.json | jq .name
"John"
❯ $ cat example2.json | jq .name | sed 's/"//g'
John

過濾 JSON object 保留特定欄位

❯ $ cat example2.json | jq '{name, skills}'
{
  "name": "John",
  "skills": [
    "Java",
    "C#",
    "Node"
  ]
}

取得第一層所有 value

如果資料是物件 (Object)

基本用法:

❯ $ cat example2.json | jq '.[]'
"John"
18
null
[
  "Java",
  "C#",
  "Node"
]
{
  "country": "Taiwan",
  "city": "Taipie",
  "district": "Xinyi"
}

將結果用陣列包起來:

❯ $ cat example2.json | jq [.[]]
[
  "John",
  18,
  null,
  [
    "Java",
    "C#",
    "Node"
  ],
  {
    "country": "Taiwan",
    "city": "Taipie",
    "district": "Xinyi"
  }
]

如果資料是陣列 (Array)

## 範例資料
❯ $ cat example3_array.json
[{"name": "Rubio","age": 32}, {"name": "Jokic","age": 28}]

## 效果
❯ $ cat example3_array.json | jq .[]
{
  "name": "Rubio",
  "age": 32
}
{
  "name": "Jokic",
  "age": 28
}

取得第一層所有 key

如果資料是物件 (Object)

會自動用一個陣列包起來:

❯ $ cat example2.json | jq keys
[
  "address",
  "age",
  "job",
  "name",
  "skills"
]

如果資料是陣列 (Array)

陣列的 key 只是流水號,意義不大:

## 範例資料
❯ $ cat example3_array.json
[{"name": "Rubio","age": 32}, {"name": "Jokic","age": 28}]

## 效果
❯ $ cat example3_array.json | jq keys
[
  0,
  1
]

陣列操作 – 擷取元素 (Element)

範例資料 example4_array.json

❯ $ cat example4_array.json
[{"name":"person1","age":11},{"name":"person2","age":22},{"name":"person3","age":33},{"name":"person4","age":44}]

擷取單一元素

指定陣列元素的 index:

❯ $ cat example4_array.json | jq .[1]
{
  "name": "person2",
  "age": 22
}

如果擷取到不存在的 index,回傳 null:

❯ $ cat example4_array.json | jq .[10]
null

擷取多個元素

可以指定多個 index:

❯ $ cat example4_array.json | jq .[1,3]
{
  "name": "person2",
  "age": 22
}
{
  "name": "person4",
  "age": 44
}

或是指定一個 index 範圍,注意這裡列印的結果已經有用陣列包起來:

❯ $ cat example4_array.json | jq .[1:3]  # 不含 element[3]
[
  {
    "name": "person2",
    "age": 22
  },
  {
    "name": "person3",
    "age": 33
  }
]

印出所有元素

❯ $ cat example4_array.json | jq .[]
{
  "name": "person1",
  "age": 11
}
{
  "name": "person2",
  "age": 22
}
{
  "name": "person3",
  "age": 33
}
{
  "name": "person4",
  "age": 44
}

陣列操作 – 擷取元素 (Element) 中的特定欄位 value

取特定一個欄位

以下 3 種寫法都可以:

❯ $ cat example4_array.json | jq .[].name
❯ $ cat example4_array.json | jq '.[]["name"]'
❯ $ cat example4_array.json | jq '.[]|.name'
"person1"
"person2"
"person3"
"person4"

如果想將結果包成新的陣列:

❯ $ cat example4_array.json | jq '[.[].name]'
❯ $ cat example4_array.json | jq '[.[]["name"]]'
❯ $ cat example4_array.json | jq '[.[]|.name]'
[
  "person1",
  "person2",
  "person3",
  "person4"
]

也可以用類似過濾的方式,將結果包成新的物件陣列:

❯ $ cat example4_array.json | jq '.[]|={name}'
[
  {
    "name": "person1"
  },
  {
    "name": "person2"
  },
  {
    "name": "person3"
  },
  {
    "name": "person4"
  }
]

取多個欄位

基本用法,單純逐一印出欄位的 value:

❯ $ cat example4_array.json | jq '.[]|.name, .age'
"person1"
11
"person2"
22
"person3"
33
"person4"
44

上面寫法即使用陣列包起來,來自同一個 element 的 value 並不會 group,如果要額外處理並不好處理:

❯ $ cat example4_array.json | jq '[.[]|.name, .age]'
[
  "person1",
  11,
  "person2",
  22,
  "person3",
  33,
  "person4",
  44
]

如果希望來自同一個 element 的 value 有 group 效果,可以這樣寫:

❯ $ cat example4_array.json | jq '.[]|[.name, .age]'
[
  "person1",
  11
]
[
  "person2",
  22
]
[
  "person3",
  33
]
[
  "person4",
  44
]

陣列操作 – 擷取元素 (Element) 中的特定欄位 value 並作字串加工

以下 3 種寫法都可以:

❯ $ cat example4_array.json | jq '"Hi " + .[]["name"]'
❯ $ cat example4_array.json | jq '"Hi " + (.[]["name"])'
❯ $ cat example4_array.json | jq '"Hi \(.[]["name"])"' # (注意要用雙引號將整個 expression 包圍起來)
"Hi person1"
"Hi person2"
"Hi person3"
"Hi person4"

map() – 對陣列元素逐一操作

jq 支援一些函數功能,其中 map() 函數可以對 Array 的每一項 Element 逐一操作,最後合併結果,在某些場景應用很強大。

取出陣列中的特定 key,將結果包成陣列

這和上面的 cat example4_array.json | jq '[.[].name]' 效果是類似的,只是用 map() 的語法更簡潔易懂:

❯ $ cat example4_array.json | jq 'map(.name)'
[
  "person1",
  "person2",
  "person3",
  "person4"
]

字串加工

❯ $ cat example4_array.json | jq 'map("Hi " + .name)'
[
  "Hi person1",
  "Hi person2",
  "Hi person3",
  "Hi person4"
]

搭配 if-else 的範例

可以做更豐富的判斷應用,例如:

❯ $ echo '[0, 1, 2, 3]' | jq 'map(if . == 0 then "零" elif . == 1 then "壹" elif . == 2 then "貳" else "其他" end)'
[
  "零",
  "壹",
  "貳",
  "其他"
]

length – 取得字串或陣列的長度

length 可以用來取得長度:

  • 字串: 字元長度
  • 陣列: element 數

取得字串長度

❯ $ echo '{"url":"onejar99.com", "name":"OneJar 的隧道"}' | jq '.url|length'
12

取得陣列長度

❯ $ cat example4_array.json
[{"name":"person1","age":11},{"name":"person2","age":22},{"name":"person3","age":33},{"name":"person4","age":44}]

❯ $ cat example4_array.json | jq '.|length'
4

split() – 切割字串

❯ $ echo '"Rubio,Jokic,Jeremy"' | jq 'split(",")'
[
  "Rubio",
  "Jokic",
  "Jeremy"
]

join() – 將陣列元素組合成一個字串

$ echo '["Rubio" ,"Jokic" ,"Jeremy"]' | jq '.|join(",")'
"Rubio,Jokic,Jeremy"

select() – 條件過濾

select() 常常搭配 map() 使用,做到動態條件過濾,只列印符合條件的 element。

範例資料 example5_array.json

❯ $ cat example5_array.json
[{"name":"Rubio","age":32},{"name":"Jokic","age":28},{"name":"Jeremy","age":34}]

條件示範:過濾掉 name 過長的人 (字串欄位)

❯ $ cat example5_array.json | jq 'map(select(.name|length <= 5))'
[
  {
    "name": "Rubio",
    "age": 32
  },
  {
    "name": "Jokic",
    "age": 28
  }
]

條件示範:過濾留下 age 大於 30 歲的人 (數字欄位)

❯ $ cat example5_array.json | jq 'map(select(.age|. > 30))'
[
  {
    "name": "Rubio",
    "age": 32
  },
  {
    "name": "Jeremy",
    "age": 34
  }
]

發表留言