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 提供了兩個內置映射函數來實現這種轉換:mapmap_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。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM