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

时间:2024-02-25 13:23:23

 

一、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。