天天看點

JSON格式化輸出和解析工具 - jq

一、jq 簡介

JSON 是一種輕量級且與語言無關的資料存儲格式,易于與大多數程式設計語言內建,也易于了解 。雖然它以 JavaScript 開頭,而且主要用于在伺服器和浏覽器之間交換資料,但現在正在用于許多領域,包括嵌入式系統。JSON是前端程式設計經常用的格式,對于PHP或Python,解析JSON很容易,尤其是PHP的json_encode和json_decode。Linux下處理JSON的神器是jq。對于JSON格式而言,jq就像sed/awk/grep這些神器一樣的友善,jq沒有亂七八糟的依賴,隻需要一個binary檔案jq就可以了。Linux 上使用指令行工具jq來解析并格式化列印 JSON,它對于在 shell 腳本中處理大型 JSON 資料或在 shell 腳本中處理 JSON 資料非常有用。

JSON 是一種輕量級的資料交換格式,其采用完全獨立于語言的文本格式,具有友善人閱讀和編寫,同時也易于機器的解析和生成。這些特性決定了 JSON 格式越來越廣泛的應用于現代的各種系統中。作為系統管理者,在日常的工作中無論是編輯配置檔案或者通過 http 請求查詢資訊,我們都不可避免的要處理 JSON 格式的資料。

jq 是一款指令行下處理 JSON 資料的工具,其可以接受标準輸入,指令管道或者檔案中的 JSON 資料,經過一系列的過濾器 (filters) 和表達式的轉後形成我們需要的資料結構并将結果輸出到标準輸出中。jq 的這種特性使我們可以很容易地在 Shell 腳本中調用它。

二、jq 安裝

jq 是開源軟體。目前大部分的Linux系統官方軟體倉庫中均有收錄。使用者可以通過系統自帶的軟體包管理器直接安裝,也可以手動從源代碼編譯安裝。

1) jq編譯安裝
jq 的源代碼可以從其代碼倉庫中獲得。編譯 jq 的指令如下:
[root@ss-server ~]# git clone https://github.com/stedolan/jq.git
[root@ss-server ~]# cd jq
[root@ss-server ~]# autoreconf -i
[root@ss-server ~]# ./configure --disable-maintainer-mode
[root@ss-server ~]# make
[root@ss-server ~]# make install

2)jq直接安裝(centos7可以直接yum安裝jq)
[root@ss-server ~]# yum install -y jq
[root@ss-server ~]# jq --version
jq-1.5      

三、jq 使用

作為一個标準的指令行工具,jq可以處理 JSON 檔案,也可以直接處理從指令行管道或者流中傳入的資料,這友善在 shell 腳本中使用。通過 jq 的 .(點)過濾器可以讓 JSON 的格式規整起來,即輸出格式化,美麗的列印效果。

1.  jq 指令行幫忙資訊

[root@ss-server ~]# jq -h
jq - commandline JSON processor [version 1.5]
Usage: jq [options] <jq filter> [file...]

        jq is a tool for processing JSON inputs, applying the
        given filter to its JSON text inputs and producing the
        filter's results as JSON on standard output.
        The simplest filter is ., which is the identity filter,
        copying jq's input to its output unmodified (except for
        formatting).
        For more advanced filters see the jq(1) manpage ("man jq")
        and/or https://stedolan.github.io/jq

        Some of the options include:
         -c             compact instead of pretty-printed output;
         -n             use `null` as the single input value;
         -e             set the exit status code based on the output;
         -s             read (slurp) all inputs into an array; apply filter to it;
         -r             output raw strings, not JSON texts;
         -R             read raw strings, not JSON texts;
         -C             colorize JSON;
         -M             monochrome (don't colorize JSON);
         -S             sort keys of objects on output;
         --tab  use tabs for indentation;
         --arg a v      set variable $a to value <v>;
         --argjson a v  set variable $a to JSON value <v>;
         --slurpfile a f        set variable $a to an array of JSON texts read from <f>;

jq 通過指令行選項來控制對輸入輸出的處理,這裡重點介紹幾個重要的選項:
1)'-r'選項。
該選項控制 jq 是輸出 raw 格式内容或 JSON 格式内容。所謂的 JSON 格式是指符合 JSON 标準的格式。
例如我們要查詢 JSON 字元串{"name":"tom"}中 name 的值. 使用-r 選項時傳回的是'tom'. 不使用-r 選項時,傳回的是'"tom"'.傳回值多了一對雙引号。

2)-s 選項。 
jq 可以同時處理空格分割的多個 JSON 字元串輸入。預設情況下,jq 會将 filter 分别對每個 JSON 輸入應用,并傳回結果。
使用-s 選項,jq會将所有的 JSON 輸入放入一個數組中并在這個數組上使用 filter。"-s"選項不但影響到 filter 的寫法。
如果在 filter 中需要對資料進行選擇和映射,其還會影響最終結果。

3)--arg 選項。
jq 通過該選項提供了和宿主腳本語言互動的能力。該選項将值(v)綁定到一個變量(a)上。在後面的 filter 中可以直接通過變量引用這個值。
例如,filter '.$a'表示查詢屬性名稱等于變量 a 的值的屬性。      

預設情況下,jq會将json格式化為多行樹狀結構輸出,但有時需要将一個json串在一行輸出,可使用-c參數,例如:

[root@ss-server ~]# cat bo.json
{
 "firstName": "John",
 "lastName": "Smith",
 "age": 25,
 "address": {
  "streetAddress": "21 2nd Street",
  "city": "New York",
  "state": "NY",
  "postalCode": "10021"
},
 "phoneNumber": [
{
 "type": "home",
 "number": "212 555-1234"
},
{
 "type": "fax",
 "number": "646 555-4567"
}
],
 "gender": {
 "type": "male"
 }
}

[root@ss-server ~]# jq -c . bo.json
{"firstName":"John","lastName":"Smith","age":25,"address":{"streetAddress":"21 2nd Street","city":"New York","state":"NY","postalCode":"10021"},
"phoneNumber":[{"type":"home","number":"212 555-1234"},{"type":"fax","number":"646 555-4567"}],"gender":{"type":"male"}}      

使用.點符号表示将輸入的 JSON 檔案格式化輸出。

如下,兩個指令執行結果是一樣的
[root@ss-server ~]# jq . bo.json
[root@ss-server ~]# cat bo.json |jq .

示例1
[root@ss-server ~]# echo -e '{\n"a":"1",\n"b":"2",\n"c":"3"\n}'
{
"a":"1",
"b":"2",
"c":"3"
}
[root@ss-server ~]# echo -e '{\n"a":"1",\n"b":"2",\n"c":"3"\n}'|jq -r '.'
{
  "a": "1",
  "b": "2",
  "c": "3"
}
[root@ss-server ~]# echo -e '{\n"a":"1",\n"b":"2",\n"c":"3"\n}'|jq -r .a
1
[root@ss-server ~]# echo -e '{\n"a":"1",\n"b":"2",\n"c":"3"\n}'|jq -r .b
2
[root@ss-server ~]# echo -e '{\n"a":"1",\n"b":"2",\n"c":"3"\n}'|jq -r .c
3

示例2
[root@ss-server ~]# head -2 rubao
{"industry": "紡織服裝、服飾業", "name": "安徽公司", "business": "201231313", "description": "空值"}
{"industry": "批發業", "name": "北京公司", "business": "32423423424", "description": "空值"}

[root@ss-server ~]# head -2 rubao|jq .
{
  "industry": "紡織服裝、服飾業",
  "name": "安徽公司",
  "business": "201231313",
  "description": "空值"
}
{
  "industry": "批發業",
  "name": "北京公司",
  "business": "32423423424",
  "description": "空值"
}

[root@ss-server ~]# head -2 rubao|jq -r '. | select(.name == "北京公司")'  
{
  "industry": "批發業",
  "name": "北京公司",
  "business": "32423423424",
  "description": "空值"
}

選取某屬性:
[root@ss-server ~]# head -2 rubao|jq .name
"安徽公司"
"北京公司"
[root@ss-server ~]# head -2 rubao|jq .business
"201231313"
"32423423424"

[root@ss-server ~]# head -2 rubao|jq -r '. | select(.name == "北京公司")|.business' 
32423423424

[root@ss-server ~]# head -2 rubao|jq -r '. | select(.industry == "批發業")|.name'          
北京公司      

高速查詢JSON資料

#### 利用jq可以以key作為keyword來對JSON作出高速查詢, 比如:
[root@ss-server ~]# jq .firstName bo.json
"John"
[root@ss-server ~]# jq -r .age bo.json       
25
[root@ss-server ~]# jq -r .address bo.json      
{
  "streetAddress": "21 2nd Street",
  "city": "New York",
  "state": "NY",
  "postalCode": "10021"
}

#### jq的鍵查詢也支援鍊式調用
[root@ss-server ~]# jq -r .address.city bo.json  
New York
[root@ss-server ~]# jq -r .address.state bo.json     
NY

#### jq的管道操作(注意下面的過濾操作)
熟悉指令行的朋友可能都知道 | (管道)是一個很強大的 武器。幸運的是,jq 也提供了對管道的支援。
[root@ss-server ~]# jq -r .address bo.json     
{
  "streetAddress": "21 2nd Street",
  "city": "New York",
  "state": "NY",
  "postalCode": "10021"
}

[root@ss-server ~]# jq -r '.address|{city}' bo.json
{
  "city": "New York"
}

[root@ss-server ~]# jq -r '.address|{postalCode}' bo.json     
{
  "postalCode": "10021"
}

去掉{}大括号
[root@ss-server ~]# jq -r '.address|.city' bo.json
New York

[root@ss-server ~]# jq -r '.address|.postalCode' bo.json    
10021

再來看看下面一例
[root@ss-server ~]# jq -r .phoneNumber[] bo.json
{
  "type": "home",
  "number": "212 555-1234"
}
{
  "type": "fax",
  "number": "646 555-4567"
}
[root@ss-server ~]# jq -r '.phoneNumber[]|{number}' bo.json
{
  "number": "212 555-1234"
}
{
  "number": "646 555-4567"
}

去掉{}大括号
[root@ss-server ~]# jq -r '.phoneNumber[]|.number' bo.json  
212 555-1234
646 555-4567      

用逗号分隔可以同時擷取json中多個key的值。但過濾出的多個值會分多行顯示。要注意除了逗号之外不能有其他字元(包括空格),例如:

[root@ss-server ~]# jq .firstName bo.json
"John"
[root@ss-server ~]# jq .firstName,.age bo.json
"John"
25
[root@ss-server ~]# jq .firstName,.age,.lastName bo.json
"John"
25
"Smith"
 
[root@ss-server ~]# jq .firstName,.age,.lastName,.address bo.json   
"John"
25
"Smith"
{
  "streetAddress": "21 2nd Street",
  "city": "New York",
  "state": "NY",
  "postalCode": "10021"
}      

對于jq解析不存在的元素,會傳回null的結果!!

[root@ss-server ~]# head -2 rubao|jq -r '.name'
安徽公司
北京公司
[root@ss-server ~]# head -2 rubao|jq -r '.address'
null
null      

jq 使用 --arg 參數來定義變量!!

如下uesr_name代表鍵,$name代表name這個變量即前面的okok,這個變量可以是互動型的

# jq --arg name okok '{uesr_name:$name}'

如下表示把shibo這個串賦給name這個變量,json的鍵為Name,值為name這個變量,即shibo

# jq -n --arg name shibo '{Name:$name}'

示例如下:
[root@ss-server ~]# jq -r '.' rubao 
{
  "industry": "紡織服裝、服飾業",
  "name": "安徽公司",
  "business": "201231313",
  "description": "空值"
}
{
  "industry": "批發業",
  "name": "北京公司",
  "business": "32423423424",
  "description": "空值"
}

[root@ss-server ~]# jq --arg name "天津公司" '{name:$name}' rubao
{
  "name": "天津公司"
}
{
  "name": "天津公司"
}

[root@ss-server ~]# jq -n --arg name "天津公司" '{name:$name}' rubao
{
  "name": "天津公司"
}

再看下面一例
[root@ss-server ~]# cat kevin 
{
 "firstName": "John",
 "lastName": "Smith",
 "age": 25,
 "address": {
  "streetAddress": "21 2nd Street",
  "city": "New York",
  "state": "NY",
  "postalCode": "10021"
},
 "phoneNumber": [
{
 "type": "home",
 "number": "212 555-1234"
},
{
 "type": "fax",
 "number": "646 555-4567"
}
],
 "gender": {
 "type": "male"
 }
}

[root@ss-server ~]# jq -r '.age' kevin
25

[root@ss-server ~]# jq --arg age 32 '{age:$age}' kevin   
{
  "age": "32"
}
[root@ss-server ~]# jq -n --arg age 32 '{age:$age}' kevin
{
  "age": "32"
}

[root@ss-server ~]# jq -n --arg age 32 '{age:$age}' kevin|jq .age
"32"      

2.  jq 解析并格式化輸出JSON資料

使用Linux上的指令行工具jq來解析并格式化列印JSON,它對于在 shell 腳本中處理大型 JSON 資料或在 shell 腳本中處理 JSON 資料非常有用。jq可以解析特定資料,要過濾出 JSON 的特定部分,首先需要了解格式化輸出的 JSON 檔案的資料層次結構。

來自 jsonip.com 的資料,使用 curl 或 wget 工具獲得 JSON 格式的外部 IP 位址。實際資料看起來類似這樣:
[root@ss-server ~]# wget -cq http://jsonip.com/ -O -
{"ip":"47.254.79.224","about":"https://jsonip.com/about","Pro!":"http://getjsonip.com","Get Notifications": "https://jsonip.com/notify"}

現在使用 jq 格式化輸出它:
[root@ss-server ~]# wget -cq http://jsonip.com/ -O -|jq -r '.'
{
  "ip": "47.254.79.224",
  "about": "https://jsonip.com/about",
  "Pro!": "http://getjsonip.com",
  "Get Notifications": "https://jsonip.com/notify"
}

當然也可以通過 Python json.tool 子產品做到!!!
[root@ss-server ~]# wget -cq http://jsonip.com/ -O -|python -m json.tool 
{
    "Get Notifications": "https://jsonip.com/notify",
    "Pro!": "http://getjsonip.com",
    "about": "https://jsonip.com/about",
    "ip": "47.254.79.224"
}

這種基于 Python 的解決方案對于大多數使用者來說應該沒問題,但是如果沒有預安裝或無法安裝 Python 則不行!!      

#####################  jq 基本過濾和辨別符功能  #####################

jq 可以從 STDIN 或檔案中讀取 JSON 資料,在使用中可以實際情況而定。

單個符号:. 是最基本的過濾器。這些過濾器也稱為對象辨別符-索引。jq 使用單個 . 過濾器基本上相當将輸入的 JSON 檔案格式化輸出。

單引号:不必始終使用單引号。但是如果你在一行中組合幾個過濾器,那麼你必須使用它們。

雙引号:你必須用兩個雙引号括起任何特殊字元,如 @、#、$,例如 jq .foo.”@bar”。

原始資料列印:不管出于任何原因,如果你隻需要最終解析的資料(不包含在雙引号内),請使用帶有 -r 标志的 jq 指令,如下所示:jq -r .foo.bar。

示例說明:

1)如下,假設test.json檔案中是我們要處理的JSON資料,那麼可以直接将檔案名傳給jq。
[root@ss-server ~]# cat test.json
[{"hostCompany":"Beijing Autelan Technology","hostModel":"CS-VIC-2000-C","hostsn":"01010730b12014A00477","mac":"00:1F:64:CE:F3:8E",
"cpuModel":"MIPS 74Kc V4.12","cpuSN":"000000","memoryModel":"abcdefg","memorySN":"000000","boardSN":"01010730b12014A00477",
"networkCardMac":"00:1F:64:CE:F3:8F","lowFreModel":"AR9344","lowFreSN":"000000","hignFreModel":"AR9582","hignFreSN":"000000",
"gpsModel":"abcdefg","gpsSN":"000000","MEID_3g":"A000004E123ABD2","Company_3g":"ZTEMT INCORPORATED","modelOf3g":"MC271X",
"snOf3g":"A000004E123ABD2","iccid":"89860314400200885980","Operators":"CTCC","hardVersion":"1.20","firmwareVersion":"1.0.6.29"}]

可以使用Python json.tool 子產品進行解析和格式化輸出
[root@ss-server ~]# cat test.json|python -m json.tool
[
    {
        "Company_3g": "ZTEMT INCORPORATED",
        "MEID_3g": "A000004E123ABD2",
        "Operators": "CTCC",
        "boardSN": "01010730b12014A00477",
        "cpuModel": "MIPS 74Kc V4.12",
        "cpuSN": "000000",
        "firmwareVersion": "1.0.6.29",
        "gpsModel": "abcdefg",
        "gpsSN": "000000",
        "hardVersion": "1.20",
        "hignFreModel": "AR9582",
        "hignFreSN": "000000",
        "hostCompany": "Beijing Autelan Technology",
        "hostModel": "CS-VIC-2000-C",
        "hostsn": "01010730b12014A00477",
        "iccid": "89860314400200885980",
        "lowFreModel": "AR9344",
        "lowFreSN": "000000",
        "mac": "00:1F:64:CE:F3:8E",
        "memoryModel": "abcdefg",
        "memorySN": "000000",
        "modelOf3g": "MC271X",
        "networkCardMac": "00:1F:64:CE:F3:8F",
        "snOf3g": "A000004E123ABD2"
    }
]

下面使用jq指令解析,下面三個指令的執行結果是一樣的!
[root@ss-server ~]# cat test.json|jq -r '.'
[root@ss-server ~]# jq . test.json
[root@ss-server ~]# jq -r '.' test.json 
[
  {
    "hostCompany": "Beijing Autelan Technology",
    "hostModel": "CS-VIC-2000-C",
    "hostsn": "01010730b12014A00477",
    "mac": "00:1F:64:CE:F3:8E",
    "cpuModel": "MIPS 74Kc V4.12",
    "cpuSN": "000000",
    "memoryModel": "abcdefg",
    "memorySN": "000000",
    "boardSN": "01010730b12014A00477",
    "networkCardMac": "00:1F:64:CE:F3:8F",
    "lowFreModel": "AR9344",
    "lowFreSN": "000000",
    "hignFreModel": "AR9582",
    "hignFreSN": "000000",
    "gpsModel": "abcdefg",
    "gpsSN": "000000",
    "MEID_3g": "A000004E123ABD2",
    "Company_3g": "ZTEMT INCORPORATED",
    "modelOf3g": "MC271X",
    "snOf3g": "A000004E123ABD2",
    "iccid": "89860314400200885980",
    "Operators": "CTCC",
    "hardVersion": "1.20",
    "firmwareVersion": "1.0.6.29"
  }
]
  
去掉test.json檔案資料中的[]方括号
[root@ss-server ~]# jq .[] test.json
{
  "hostCompany": "Beijing Autelan Technology",
  "hostModel": "CS-VIC-2000-C",
  "hostsn": "01010730b12014A00477",
  "mac": "00:1F:64:CE:F3:8E",
  "cpuModel": "MIPS 74Kc V4.12",
  "cpuSN": "000000",
  "memoryModel": "abcdefg",
  "memorySN": "000000",
  "boardSN": "01010730b12014A00477",
  "networkCardMac": "00:1F:64:CE:F3:8F",
  "lowFreModel": "AR9344",
  "lowFreSN": "000000",
  "hignFreModel": "AR9582",
  "hignFreSN": "000000",
  "gpsModel": "abcdefg",
  "gpsSN": "000000",
  "MEID_3g": "A000004E123ABD2",
  "Company_3g": "ZTEMT INCORPORATED",
  "modelOf3g": "MC271X",
  "snOf3g": "A000004E123ABD2",
  "iccid": "89860314400200885980",
  "Operators": "CTCC",
  "hardVersion": "1.20",
  "firmwareVersion": "1.0.6.29"
}
 
下面進行過濾操作
[root@ss-server ~]# jq -r .[].hostModel test.json 
CS-VIC-2000-C
 
[root@ss-server ~]# jq -r .[].mac test.json
00:1F:64:CE:F3:8E
  
[root@ss-server ~]# jq -r '.[] | .mac' test.json
00:1F:64:CE:F3:8E
  
[root@ss-server ~]# jq -r '.[] |.mac, .gpsSN' test.json
00:1F:64:CE:F3:8E
000000
  
[root@ss-server ~]# jq -r '.[] |.mac, .gpsSN' test.json
00:1F:64:CE:F3:8E
000000
 
[root@ss-server ~]# jq .[] test.json |jq .mac
"00:1F:64:CE:F3:8E"
[root@ss-server ~]# jq .[] test.json |jq .hostModel
"CS-VIC-2000-C"
[root@ss-server ~]# jq .[] test.json |jq .gpsModel
"abcdefg"
 
#############################################################################
2)如下kevin檔案
[root@ss-server ~]# cat kevin
{
 "firstName": "John",
 "lastName": "Smith",
 "age": 25,
 "address": {
  "streetAddress": "21 2nd Street",
  "city": "New York",
  "state": "NY",
  "postalCode": "10021"
},
 "phoneNumber": [
{
 "type": "home",
 "number": "212 555-1234"
},
{
 "type": "fax",
 "number": "646 555-4567"
}
],
 "gender": {
 "type": "male"
 }
}

使用python json.tool子產品格式化輸出
[root@ss-server ~]# cat kevin | python -m json.tool
{
    "address": {
        "city": "New York",
        "postalCode": "10021",
        "state": "NY",
        "streetAddress": "21 2nd Street"
    },
    "age": 25,
    "firstName": "John",
    "gender": {
        "type": "male"
    },
    "lastName": "Smith",
    "phoneNumber": [
        {
            "number": "212 555-1234",
            "type": "home"
        },
        {
            "number": "646 555-4567",
            "type": "fax"
        }
    ]
}
 
使用jq工具進行格式化輸出,比如從上面kevin檔案中過濾出位址(一個{}括号是一個json資料元素)
[root@ss-server ~]# jq .address kevin
{
  "streetAddress": "21 2nd Street",
  "city": "New York",
  "state": "NY",
  "postalCode": "10021"
}

或者
[root@ss-server ~]# cat kevin | python -m json.tool|jq .address
{
  "city": "New York",
  "postalCode": "10021",
  "state": "NY",
  "streetAddress": "21 2nd Street"
}
 
想要郵政編碼,然後要添加另一個"對象辨別符-索引"" ,即另一個過濾器。
[root@ss-server ~]# cat kevin |jq .address.postalCode
"10021"
 
如果要想取gender中的type
[root@ss-server ~]# cat kevin|jq .gender
{
  "type": "male"
}
[root@ss-server ~]# cat kevin|jq .gender.type
"male"
 
在接着看:
[root@ss-server ~]# cat kevin|jq .phoneNumber
[
  {
    "type": "home",
    "number": "212 555-1234"
  },
  {
    "type": "fax",
    "number": "646 555-4567"
  }
]
 
去掉方括号
[root@ss-server ~]# cat kevin|jq .phoneNumber[]
{
  "type": "home",
  "number": "212 555-1234"
}
{
  "type": "fax",
  "number": "646 555-4567"
}
 
[root@ss-server ~]# cat kevin|jq .phoneNumber[].type
"home"
"fax"
[root@ss-server ~]# cat kevin|jq .phoneNumber[].number
"212 555-1234"
"646 555-4567"      

需要注意:

1)輸入内容必須嚴格遵循JSON格式的标準,所有的屬性名必須是以雙引号包括的字元串。

2)對象的最後一個屬性的末尾或者數組的最後一個元素的末尾不能有逗号,否則jq會抛出無法解析JSON的錯誤!

3)過濾器區分大小寫 ,并且必須使用完全相同的字元串來擷取有意義的輸出,否則就是 null。

######  從JSON 數組中解析元素  ######

JSON 數組的元素包含在方括号内,這無疑是非常通用的!!要解析數組中的元素,則必須使用 [] 辨別符以及其他對象辨別符索引。在上面第二個示例kevin檔案中的JSON 資料中,電話号碼存儲在數組中,要從此數組中擷取所有内容,隻需使用括号,如下這個示例:

[root@ss-server ~]# jq .phoneNumber[] kevin
{
  "type": "home",
  "number": "212 555-1234"
}
{
  "type": "fax",
  "number": "646 555-4567"
}
      

假設現在隻想要數組的第一個元素,然後使用從 0 開始的數組對象編号,對于第一個項目,使用 [0] ,對于下一個項目,它應該每步增加 1。如下:

[root@ss-server ~]# jq .phoneNumber[0] kevin
{
  "type": "home",
  "number": "212 555-1234"
}
[root@ss-server ~]# jq .phoneNumber[1] kevin
{
  "type": "fax",
  "number": "646 555-4567"
}
      

######  腳本程式設計示例  ######

如上示例2中kevin檔案資料,假設現在隻想要家庭電話,而不是整個JSON數組資料。這就是用 jq 指令腳本編寫的友善之處。

[root@ss-server ~]# cat kevin | jq -r '.phoneNumber[]'
{
  "type": "home",
  "number": "212 555-1234"
}
{
  "type": "fax",
  "number": "646 555-4567"
}
[root@ss-server ~]# cat kevin | jq -r '.phoneNumber[] | select(.type == "home") | .number'
212 555-1234
      

上面指令中,首先将一個過濾器的結果傳遞給另一個,然後使用 select 屬性選擇特定類型的資料,再次将結果傳遞給另一個過濾器。

######  jq内建函數  ######

jq 還有一些内建函數如 key,has。其中:

key 是用來擷取JSON中的key元素的:

has 是用來是判斷是否存在某個key:

[root@ss-server ~]# cat kevin 
{
 "firstName": "John",
 "lastName": "Smith",
 "age": 25,
 "address": {
  "streetAddress": "21 2nd Street",
  "city": "New York",
  "state": "NY",
  "postalCode": "10021"
},
 "phoneNumber": [
{
 "type": "home",
 "number": "212 555-1234"
},
{
 "type": "fax",
 "number": "646 555-4567"
}
],
 "gender": {
 "type": "male"
 }
}


### key是用來擷取JSON中的key元素的:
[root@ss-server ~]# cat kevin |jq 'keys'
[
  "address",
  "age",
  "firstName",
  "gender",
  "lastName",
  "phoneNumber"
]

### has是用來是判斷是否存在某個key: 
[root@ss-server ~]# cat kevin |jq 'has("firstName")'
true
[root@ss-server ~]# cat kevin |jq 'has("nextName")' 
false      

###### jq解析案例:将sql自動部署版本包上傳到制品庫的檢查過程  ######

[root@localhost ~]# curl -u xlyadmin:password -s -T /root/app/ONLINE/AMS/sql/1.0.8.1/ams.sql.tar "http://192.168.10.14:8040/artifactory/kevin-local/bxbank/mmjk/sql/sql/1.0.8.1/ams.sql.tar"
{
  "repo" : "kevin-local",
  "path" : "/bxbank/mmjk/sql/sql/1.0.8.1/ams.sql.tar",
  "created" : "2019-12-10T17:17:23.549+08:00",
  "createdBy" : "xlyadmin",
  "downloadUri" : "http://192.168.10.14:8040/artifactory/kevin-local/bxbank/mmjk/sql/sql/1.0.8.1/ams.sql.tar",
  "mimeType" : "application/x-tar",
  "size" : "30720",
  "checksums" : {
    "sha1" : "7026b3997bd464360c88de96af5ed9f2481edabf",
    "md5" : "4f09e77c9082c75ccfd2fa92f0927a11",
    "sha256" : "cba5aa9e114f65966a67adbe96cbcef3a98e68f3410b4d14ad4445c8ef48e19f"
  },
  "originalChecksums" : {
    "sha256" : "cba5aa9e114f65966a67adbe96cbcef3a98e68f3410b4d14ad4445c8ef48e19f"
  },
  "uri" : "http://192.168.10.14:8040/artifactory/kevin-local/bxbank/mmjk/sql/sql/1.0.8.1/ams.sql.tar"
}
 
[root@localhost ~]# Result=$(curl -u xlyadmin:password -s -T /root/app/ONLINE/AMS/sql/1.0.8.1/ams.sql.tar "http://192.168.10.14:8040/artifactory/kevin-local/bxbank/mmjk/sql/sql/1.0.8.1/ams.sql.tar")
 
[root@localhost ~]# echo ${Result}|jq .      
{
  "uri": "http://192.168.10.14:8040/artifactory/kevin-local/bxbank/mmjk/sql/sql/1.0.8.1/ams.sql.tar",
  "originalChecksums": {
    "sha256": "cba5aa9e114f65966a67adbe96cbcef3a98e68f3410b4d14ad4445c8ef48e19f"
  },
  "repo": "kevin-local",
  "path": "/bxbank/mmjk/sql/sql/1.0.8.1/ams.sql.tar",
  "created": "2019-12-10T17:17:23.549+08:00",
  "createdBy": "xlyadmin",
  "downloadUri": "http://192.168.10.14:8040/artifactory/kevin-local/bxbank/mmjk/sql/sql/1.0.8.1/ams.sql.tar",
  "mimeType": "application/x-tar",
  "size": "30720",
  "checksums": {
    "sha256": "cba5aa9e114f65966a67adbe96cbcef3a98e68f3410b4d14ad4445c8ef48e19f",
    "md5": "4f09e77c9082c75ccfd2fa92f0927a11",
    "sha1": "7026b3997bd464360c88de96af5ed9f2481edabf"
  }
}
 
[root@localhost ~]# echo ${Result}|jq '.errors[0].message'      
null
 
編寫檢查腳本
[root@localhost ~]# vim errorcheck.sh
#!/bin/bash
 
DATE=`date +%Y%d%m-%H%M%S`
#json傳回串中ERRORS檢查
function ErrorCheck() {
    JsonStr=$1
    UpFile=$2
    echo ${JsonStr}|jq .
    echo
    ErrorMsg=`echo ${JsonStr}|jq '.errors[0].message'`
    if [ "${ErrorMsg}" != "null"     ];then
        echo "[Failed] ${DATE} [${UpFile}] 上傳制品庫錯誤,錯誤資訊: [${ErrorMsg}]"
#        exit 1
    else
        echo "[Success] ${DATE} [${UpFile}] 上傳制品庫成功"
    fi
}
 
ErrorCheck "${Result}" "kevin-local/bxbank/mmjk/sql/sql/1.0.8.1/ams.sql.tar"
 
執行檢查腳本
[root@localhost ~]# chmod errorcheck.sh
[root@localhost ~]# sh errorcheck.sh
[ Success ][20191210-181305][/sql/1.0.38.1/edw.sql.tar] 上傳制品庫成功      

3.  jq 表達式

從jq 的指令行幫助中可以看出,在調用 jq 處理 JSON 資料時有一個必須的部分"jq filters"。實際上,jq 内部建構了一個簡易的,功能完備的語言系統。使用者在使用 jq 時,需要使用 jq 支援的文法來建構表達式(filters)并将其傳給 jq。 jq 根據文法規則解析表達式并應用在輸入的 JSON 資料上進而得到需要的結果。

jq 表達式支援串行化操作。一個複雜的表達式可以有多個簡單的,以"|"符号分割的,串行化執行的表達式組成。每個表達式以其前邊表達式的結果為輸入。例如:有 JSON 資料{"name":{"firstname":"Tom","lastname":"Clancy"}}。我們要查詢 lastname 屬性可以使用表達式'.name|.lastname'。為了友善處理 JSON 資料,jq 提供了以下幾點特性支援:

1)jq 内建了對 JSON 标準中各種資料類型的支援;

2)jq 内建了多種操作符和函數來進行資料的選擇和轉換;

3)jq 支援自定義函數和子產品化系統。我們可以自定義自己的函數庫,并在 jq 表達式中引用。

3.1  基礎表達式

基礎表達式(Basic filters)是 jq 提供的基本過濾器,用來通路 JSON 對象中的屬性。基礎表達式也是實作更複雜查詢功能的基礎。基礎表達式主要有以下幾種:

1.  '.' 符号。單獨的一個'.'符号用來表示對作為表達式輸入的整個 JSON 對象的引用。

2.  JSON 對象操作。jq 提供兩種基本表達式用來通路 JSON 對象的屬性:'.<attributename>'和'.<attributename>?'。正常情況下,這兩個表達式的行為相同:都是通路對象屬性,如果 JSON 對象不包含指定的屬性則傳回 null。差別在于,當輸入不是 JSON 對象或數組時,第一個表達式會抛出異常。第二個表達式無任何輸出。

3.  數組操作。jq 提供三種基礎表達式來操作數組:

     - 疊代器操作('.[]'). 該表達式的輸入可以是數組或者 JSON 對象。輸出的是基于數組元素或者 JSON 對象屬性值的 iterator。

     - 通路特定元素的操作('.[index]'或'.[attributename]')。用來通路數組元素或者 JSON 對象的屬性值。輸出是單個值

     - 數組切片操作('.[startindex:endindex]'),其行為類似于 python 語言中數組切片操作。

4.  表達式操作(','和 '|')。表達式操作是用來關聯多個基礎表達式。其中逗号表示對同一個輸入應用多個表達式。管道符表示将前一個表達式的輸出用作後一個表達式的輸入。目前一個表達式産生的結果是疊代器時,會将疊代器中的每一個值用作後一個表達式的輸入進而形成新的表達式。例如'.[]|.+1', 在這個表達式中,第一個子表達式'.[]'在輸入數組上建構疊代器,第二個子表達式則在疊代器的每個元素上加 1。

3.2  内置運算支援

jq 内部支援的資料類型有:數字,字元串,數組和對象(object)。并且在這些資料類型的基礎上, jq 提供了一些基本的操作符來實作一些基本的運算和資料操作。列舉如下:

1.  數學運算。對于數字類型,jq 實作了基本的加減乘除(/)和求餘(%)運算。對于除法運算,jq 最多支援 16 位小數。

2.  字元串操作。jq 提供字元串的連接配接操作(運算符為'+',例如:"tom "+"jerry"結果為"tom jerry"),字元串的複制操作(例如:'a'*3 結果為'aaa'),以及字元串分割操作(将字元串按照指定的分割符分成數組,例如"sas"/"s"的結果為["","a",""],而"sas"/"a"的結果為["s","s"]。

3.  數組操作。jq 提供兩種數組運算:并集('+')運算,結果數組中包含參與運算的數組的所有元素。差集運算('-'),例如:有數組 a,b, a-b 的結果為所有在 a 中且不包含在 b 中的元素組成的數組。

4.  對象操作。jq 實作了兩個 JSON 對象的合并操作(merge)。當兩個參與運算的對象包含相同的屬性時則保留運算符右側對象的屬性值。有兩種合并運算符:'+'和'*'。所不同的是,運算符'+'隻做頂層屬性的合并,運算符'*'則是遞歸合并。例如:有對象 a={"a":{"b":1}}, b={"a":{"c":2}},a+b 的結果為{"a":{"c":2}},而 a*b 的結果為{"a":{"b":1,"c":2}}

5.  比較操作:jq 内部支援的比較操作符有==, !=,>,>=,<=和<。其中,'=='的規則和 javascript 中的恒等('===')類似,隻有兩個操作數的類型和值均相同時其結果才是 true。

6.  邏輯運算符: and/or/not。在 jq 邏輯運算中,除了 false 和 null 外,其餘的任何值都等同于 true。

7.  預設操作符('//')。表達式'a//b'表示當表達式 a 的值不是 false 或 null 時,a//b 等于 a,否則等于 b。

######  疊代器運算  ######

jq 中有一種很特殊的運算規則:當運算符的一個或兩個操作數是疊代器時,其運算以類似與笛卡爾乘積的方式進行,即把兩個操作數中的每一個元素拿出來分别運算。例如:

#result is 5 6 7 8
[root@ss-server ~]# jq -n '([1,2]|.[])+([4,6]|.[])'
5
6
7
8
      

jq 内部支援兩種控制結構:判斷語句和異常處理。

1)判斷語句的完整結構為"if then-elif then-else-end"。當判斷條件的結果為多個值時(疊代器),會對每個值執行一次判斷。

2)異常處理語句的結構為"try <表達式 a> catch <表達式 b>"。當表達式 a 發生異常時,執行表達式 b,且輸入為捕捉到的異常資訊。如果不需要額外的處理,隻是簡單的抑制異常資訊的輸入,可以沒有 catch 語句(如 try .a)。這時,整個表達式可以簡寫為'<表達式 a>?'(如:.a?)。

jq 内部還支援函數。在使用 jq 函數時,應該注意區分兩個概念:輸入和參數。輸入可能是整個表達式的輸入資料也可能是表達式别的部分的輸出。而參數和函數一起構成新的 filter 來處理輸入。和其他程式設計語言不同的是,在調用函數時,多個參數之間以分号分隔。jq 通過内置函數提供了資料處理時常用的操作,如過濾,映射,路徑操作等。

######  映射操作  ######

在資料處理過程中,經常需要将資料從一種形式轉換成另外一種形式,或者改變資料的值。jq 提供了兩個内置映射函數來實作這種轉換:map 和 map_values。其中,map 處理的對象是數組,而 map_values 則處理對象屬性的值。map 函數的參數為 filter 表達式。在該 filter 表達式中,'.'代表被映射的元素或值。

### map 函數
輸入:[1,2,3,4]
jq 表達式:jq -r 'map(.+1)'
輸出:[2,3,4,5]
      

######  過濾操作  ######

在 jq 中有兩種類型的選擇過濾操作。

第一種是基于資料類型的過濾,如表達式 '.[]|arrays' 的結果隻包含數組。可以用來過濾的類型過濾器有:arrays, objects, iterables, booleans, numbers, normals, finites, strings, nulls, values, scalars。

第二種是 select 函數。select 接受一個條件表達式作為參數。其輸入可以是疊代器,或者和 map 函數配合使用來處理數組。當輸入中的某個元素使 select 參數中的條件表達式結果為真時,則在結果中保留該元素,否則不保留該元素。

### select 函數
輸入:[1,2,3,4]
表達式:jq -r 'map(select(.>2))'
輸出:[3,4]
表達式:jq -r '.[]|select(.>2)'
輸出:3 4
      

######  路徑操作  ######

在 jq 中的 path 也是指從根到某個目錄屬性的通路路徑。在 jq 中有兩種表示路徑的方式:數組表示法和屬性表示法。屬性表示法類似于在 filter 中通路某個屬性值的方式,如'.a.b'。數組表示法是将路徑中的每一部分表示為數組的一個元素。jq 提供了一個内置函數 path 用來實作路徑從屬性表示法到數組表示法的轉換。

jq 還提供了函數用來讀取路徑的值(getpath), 設定路徑的值(setpath)和删除路徑(del)。不過遺憾的是,這三個函數對路徑的處理并不一緻。其中 getpath 和 setpath 隻接受數組表示法的路徑,而 del 函數隻能正确處理屬性表示法的路徑。

jq 還提供了一個函數 paths 用來枚舉可能存在的路徑。在沒有參數的情況下,paths 函數将輸出 JSON 資料中所有可能的路徑。paths 函數可以接受一個過濾器,來隻輸出滿足條件的路徑。

######  存在判斷函數  ######

jq 中提供了一系列的函數用來判斷某個元素或者屬性是否存在于輸入資料中。其中函數 has 和 in 用來判斷 JSON 對象或數組是否包含特定的屬性或索引。函數 contains 和 inside 用來判斷參數是否完全包含在輸入資料中。對于不同的資料類型,判斷是否完全包含的規則不同。對于字元串,如果 A 是 B 的子字元串,則認為 A 完全包含于 B。對于對象類型,如果對象 A 的所有屬性在對象 B 中都能找到且值相同,則認為 A 完全包含于 B。

######  數組函數  ######

除了前面講述的基本操作符外,jq 提供内置函數用于完成數組的扁平化(flatten),反序(reverse),排序(sort, sort_by),比較(min,min_by,max,max_by) 和 查找(indices,index 和 rindex)。其中 indices 函數的輸入資料可以是數組,也可以是字元串。和 index 函數不同的是,其結果是一個包含所有參數在輸入資料中位置的數組。

### jq 中的數組函數

數組的扁平化
[root@ss-server ~]# jq -nr '[1,[2,3],4]|flatten'
[
  1,
  2,
  3,
  4
]

數組的反序
[root@ss-server ~]# jq -nr '[1,2,3]|reverse'
[
  3,
  2,
  1
]

數組排序:升序
[root@ss-server ~]# jq -nr '[3,1,2]|sort'
[
  1,
  2,
  3
]

數組排序:升序
[root@ss-server ~]# jq -nr '[{"a":1},{"a":2}]|sort_by(.a)'
[
  {
    "a": 1
  },
  {
    "a": 2
  }
]

數組的查找
[root@ss-server ~]# jq -nr '"abcb"|indices("b")'
[
  1,
  3
]

[root@ss-server ~]# jq -nr '[1,3,2,3]|indices(3)'
[
  1,
  3
]

[root@ss-server ~]# jq -nr '[1,"a",2,3,"a",4,5,"b",8,9]|indices("a")'
[
  1,
  4
]
      

4.  jq 進階特性

4.1  變量

jq 内部支援兩種變量的定義方式:

第一種定義方式:在前邊 jq 的使用部分講過,可以通過指令行參數(--arg)定義。這種方式用來從外部(如:shell)傳入資料以供 filter 表達式使用。

第二種定義方式:在 jq 表達式内部,可以自己聲明變量用來儲存表達式的結果以供表達式其餘部分使用。

jq 中定義變量的語句為:fiterexp as $variablename

######  定義和使用變量  ######

### 在下面的表達式中變量$arraylen 用來儲存數組長度,整個表達式結果為 4
[root@ss-server ~]# jq -nr '[1,2,3]'
[
  1,
  2,
  3
]
[root@ss-server ~]# jq -nr '[1,2,3]|length as $arraylen|$arraylen+1'
4

### 還可以同時定義多個變量
[root@ss-server ~]# jq -nr '{"firstname":"shibo","lastname":"kevin"}'
{
  "firstname": "shibo",
  "lastname": "kevin"
}

[root@ss-server ~]# jq -nr '{"firstname":"shibo","lastname":"kevin"}|. as {firstname:$fn, lastname:$ln}|"author is "+$fn+" "+$ln'
author is shibo kevin
[root@ss-server ~]# jq -nr '{"firstname":"shibo","lastname":"kevin"}|. as {firstname:$fn, lastname:$ln}|"author is "+$fn+" 和 "+$ln'
author is shibo 和 kevin      

jq 中同樣存在變量作用域問題。在 jq 中,有兩種方法分隔變量作用域:

第一種方法:用括号包圍部分表達式。括号内部的表達式與外部的表達式不在同一個作用域範圍内。

第二種方法:定義函數。預設情況下,聲明的變量對其後的表達式可見。但是,如果變量在特定作用域内聲明,則對作用域外部的表達式不可見。

######  變量作用域  ######

### 下面會抛出 arraylen 沒定義的異常
[root@ss-server ~]# jq -nr '[1,2,3]|(length as $arraylen|$arraylen)|$arraylen+1'
jq: error: arraylen/0 is not defined at <top-level>, line 1:
[1,2,3]|(length as $arraylen|$arraylen)|$arraylen+1                                        
jq: 1 compile error

### 下面正常執行,結果為 4.
[root@ss-server ~]# jq -nr '[1,2,3]|(length as $arraylen|$arraylen+1)'
4

### 函數作用域。該表達式會抛出異常,因為變量$fn 是在函數 fname 中定義,對最後一個子表達式##來說,$fn 是不可見的。
[root@ss-server ~]# jq -nr '{"firstname":"shibo","lastname":"kevin"}|def fname:. as {firstname:$fn, lastname:$ln}|$fn; fname|$fn'         
jq: error: fn/0 is not defined at <top-level>, line 1:
{"firstname":"shibo","lastname":"kevin"}|def fname:. as {firstname:$fn, lastname:$ln}|$fn; fname|$fn 

[root@ss-server ~]# jq -nr '{"firstname":"shibo","lastname":"kevin"}|. as {firstname:$fn, lastname:$ln}|$fn'           
shibo
      

4.2  Reduce

jq有一種特殊的資料類型:疊代器。通常有疊代器參與的運算,其結果也是一個疊代器。jq 提供了一些特殊的文法和内置函數用來縮減疊代器運算結果的個數。

reduce 關鍵字用來通過運算将疊代器的所有值合并為一個值。其調用形式為:reduce <itexp> as $var (INIT; UPDATE)。其中,表達式 itexp 産生的疊代器被指派給變量 var, UPDATE 是關于變量 var 的表達式。INIT 是該表達式的初始輸入。相對于 itexp 結果中的每個元素,UPDATE 表達式被調用一次,計算出結果用作下一次 UPDATE 調用的輸入。

######  reduce 關鍵字  ######

[root@ss-server ~]# jq -nr 'reduce ([1,2,3]|.[]) as $item (0; .+$item)'
6

上面的表達式等同于
[root@ss-server ~]# jq -nr '0 | (3 as $item|.+$item)|(2 as $item | . + $item)|(1 as $item | . + $item)'
6      

關鍵字 foreach 的作用和 reduce 類似。其調用形式為 foreach EXP as $var (INIT; UPDATE; EXTRACT)。和 reduce 關鍵字不同的是,foreach 關鍵字的每次疊代是先調用 UPDATE 再調用 EXTRACT,并以一個疊代器保留每一次的中間結果。該疊代器最後作為整個表達式的結果輸出。

######  foreach 關鍵字  ######

[root@ss-server ~]# jq -nr 'foreach ([1,2,3]|.[]) as $item (0; .+$item;.)'
1
3
6      

内置函數 limit(n;exp)用來取得表達式 exp 結果的前 n 個值。

内置函數 first, last 和 nth。這幾個函數用來取疊代器中某一個特定的元素。這幾個函數既可以以函數的形式調用,也可以作為子表達式調用。

######  firs, last 和 nth  ######

[root@ss-server ~]# jq -nr '[1,2,3]|.[]'
1
2
3

#下面的表達式按照函數的形式調用 first,結果為 1
[root@ss-server ~]# jq -nr 'first([1,2,3]|.[])'
1

#nth 函數的使用,結果為 2
[root@ss-server ~]# jq -nr 'nth(1;[1,2,3]|.[])'
2      

5.  jq 自定義函數和子產品化

作為一個類似于程式設計語言的表達式系統,jq 也提供了定義函數的能力。其文法規則為:def funcname(arguments) : funcbodyexp; 在定義函數時,需要注意下面幾條規則。

-  函數名或者參數清單後面應該跟冒号以标志函數體開始。

-  如果不需要參數,可以直接把整個參數清單部分省去。

-  參數清單中,參數之間以分号(";")分隔。

-  函數體隻能是一個表達式,且表達式需以分号結尾。

-  如果在表達式内部定義函數,整個子表達式部分不能隻包含函數定義,否則 jq 會抛出文法錯誤。

在很多情況下,函數的參數都是被當作表達式引用的,類似于程式設計其他語言中的 callback 函數。

######  map函數  ######

# def map(f): [.[] | f];
#下面表達式的結果是 20,因為當作參數傳入的表達式在函數 foo 中被引用兩次
[root@ss-server ~]# jq -nr '5|def foo(f): f|f;foo(.*2)' 
20      

如果希望傳入的參數隻被當作一個簡單的值來使用,則需要把參數的值定義為一個同名變量,并按照使用變量的方式引用。

######  值參數  ######

#下面表達式結果為 10,傳入的表達式'.*2'在函數 foo 中首先被求值。
[root@ss-server ~]# jq -nr '5|def foo(f): f as $f|$f|$f;foo(.*2)'
10

#上面的表達式可以簡寫為如下形式, 注意:引用參數時必須帶$!
[root@ss-server ~]# jq -nr '5|def foo($f): $f|$f;foo(.*2)'       
10

#否則等于直接引用參數中的表達式。
#例如下面的表達式結果為 20
[root@ss-server ~]# jq -nr '5|def foo($f): $f|f;foo(.*2)' 
20      

函數内部可以定義子函數。利用這個特性我們可以實作遞歸函數。

######  遞歸函數實作數組求和  ######

[root@ss-server ~]# jq -nr '[1,2,3,4,5]|def total: def _t: .|first+(if length>1 then .[1:]|_t else 0 end); _t;total'
15      

除了在表達式内部定義函數外,還可以把自定義函數寫在外部檔案中形成單獨的類庫。jq 有一套完整的子產品系統來支援自定義類庫。

1)首先可以通過指令行參數'-L'來指定 jq 搜尋子產品時需要搜尋的路徑。

2)其次在子產品内部,可以通過 import 指令和 include 指令來實作互相引用。在引用指令中,有幾個特殊的路徑字首需要說明。

      '~', 表示目前使用者的 home 目錄

      '$ORIGIN',表示 jq 指令的可執行檔案所在的目錄

      '.',表示目前目錄,該字首隻能用在 include 指令中。

當通過 import 指令引用一個子產品 foo/bar 時, jq 會在搜素路徑中查找 foo/bar.jq 或者 foo/bar/bar.jq。

*************** 當你發現自己的才華撐不起野心時,就請安靜下來學習吧!***************