JQ 入门教程

在 TiDB 中,PD Control 是 PD 的命令行工具,用于获取集群状态信息和调整集群。 pd-ctl 部分默认结果输出是 json 格式,在处理 TiDB 的问题时,要获取一些 region 和 store 的某些信息时,json 格式的结果不直观且直接处理起来不方便。这也是本文行文的由来,jq 可以对 json 数据进行分片、过滤、映射和转换,可以灵活的处理 Json 结构,并且用户可以根据实际需要来定制输出结果,文章内容主要介绍了 jq 的基本语法以及常见命令,供大家参考。

基础部分

创建

$ jq -n {store:1}
{
  "store": 1
}
 
$ cat 1.json
{
	"id": 4026,
	"store_id": 4,
	"capactiy": 50
}
$ cat 1.json |jq type
"object"
 
$ cat  2.txt |jq
[
  {
    "id": 4026,
    "store_id": 1
  },
  {
    "id": 4027,
    "store_id": 4
  },
  {
    "id": 4028,
    "store_id": 7
  }

添加

$ cat 1.json |jq  '. + {"version":4}'
{
  "id": 4026,
  "store_id": 4,
  "capactiy": 50,
  "version": 4
}
 
$ cat  2.txt |jq ". + [{"id":4000,"store_id":8}]"
[
  {
    "id": 4026,
    "store_id": 1
  },
  {
    "id": 4027,
    "store_id": 4
  },
  {
    "id": 4028,
    "store_id": 7
  },
  {
    "id": 4000,
    "store_id": 8
  }
]
  • “.” :在 jq 里面 . 是一个最绝对的过滤器,会把输入作为输出原样打印输出出来。

  • “|” : 运算符通过将左侧一个的输出馈入右侧一个的输入来组合两个过滤器,它与Unix shell的管道几乎相同。

  • “+”

  • 数字是通过常规算术相加的。
  • 通过将数组串联成更大的数组来添加数组。
  • 串通过被结合成一个较大的字符串添加。
  • 通过合并来添加对象,即将两个对象的所有键值对插入单个组合的对象中。如果两个对象都包含同一个键​​的值,则 + 获胜者位于右侧。(对于递归合并,请使用*运算符)

更新

$ cat 1.json |jq  '.store_id=5'
{
  "id": 4026,
  "store_id": 5,
  "capactiy": 50
}
 
$ cat  4.txt
[1,4,7]
 
$ cat 4.txt |jq type
"array"
 
 
$ cat  4.txt |jq ".[1] = 5"
[
  1,
  5,
  7
]

删除

$ cat  1.json |jq "del(.capacity)"
{
  "id": 4026,
  "store_id": 4
}
 
$ cat 2.txt  |jq  ".-[{"id":4028,"store_id":7}]"
[
  {
    "id": 4026,
    "store_id": 1
  },
  {
    "id": 4027,
    "store_id": 4
  }
]
  • del
  • 内置函数del从对象中删除键及其对应的值
  • 除了对数字进行常规的算术减法外,- 运算符还可用于数组,以从第一个数组中删除所有出现的第二个数组元素。

查询

$ cat 3.json
[{"id":6014,"store_id":1},{"id":6015,"store_id":4},{"id":6016,"store_id":7}]
$ cat 3.json  |jq ".[]"
{
  "id": 6014,
  "store_id": 1
}
{
  "id": 6015,
  "store_id": 4
}
{
  "id": 6016,
  "store_id": 7
}
$ cat 3.json  |jq ".[]|select (.id >= 6015)"
{
  "id": 6015,
  "store_id": 4
}
{
  "id": 6016,
  "store_id": 7
}
 
 
$ cat 4.txt
[1,4,7]
$ cat 4.txt |jq '.[1]'
4
$ cat 4.txt |jq '.|map(select (.>=2))'
[
  4,
  7
]
 
$ cat  2.txt |jq type
"array"
$  echo '[0, false, [], {}, null, "hello"]' |jq 'map(type)'
[
  "number",
  "boolean",
  "array",
  "object",
  "null",
  "string"
]
  • 类型
  • {}:用于构造对象
  • []:构造列表
  • map(x) & map_values(x)
  • 对于任何filter x,map(x)将对输入数组的每个元素运行该过滤器,并在新数组中返回输出,等价于 [.[] | x]
  • map_values(x):将为每个元素运行该过滤器,但在传递对象时将返回一个对象。map_values(x)被定义为.[] |= x。

常见命令讲解

取出 store 输出信息里面的 id、address、state_name 三项

» store --jq=".stores[].store | { id, address, state_name}"
  • 在 jq 里面 . 是一个最绝对的过滤器,会把输入作为输出原样打印输出出来。
    .stores 是最有用过滤器。当给定 JSON 对象(又名字典或哈希)作为输入时,它将在键 “stores” 处生成值;如果不存在,则返回 null。stores[] 和 store 的区别在于,使用 stores[] 的时候,会将拿到的结果作为一个个 json 格式的对象输出显示,不带 [] 的时候结果是放在一个大列表里,其中每个 json 对象是列表里一个元素。在使用 .stores 或者 .store 的时候,那么 . 代表的对象必须是一个 json 对象,否则会报错:“ Cannot index array with string “store”” 。在使用 .stores 之后,输出的对象是一个列表对象,那么再次使用 .stores.store 是会报错的。在 .stores[].store 中,store(非 stores)前面的 . 代表 .stores[] 的输出,所以可以表达成在 .stores[] 的输出对象里取 key 值为 store 的信息。state_alias:.state_name 给 state_name 取别名输出。
  • 在上面命令中,可以看作是经过两次处理之后再次对结果集进行处理。“| { id, address, state_name}” 结果是在前面处理之后的 json 对象里取 key 为 id,address,state_name 字段信息,同时以 json 格式输出。{} 用于构造对象(又称字典或哈希), [] 用于构造数组,所有表达式产生的所有结果都被收集到一个大数组中。
» store --jq=".stores[].store | { id, address, state_name}"
{"id":1,"address":"172.16.5.172:25266","state_name":"Up"}
{"id":4,"address":"172.16.4.235:25266","state_name":"Up"}
{"id":7,"address":"172.16.4.237:25266","state_name":"Up"}
{"id":76,"address":"172.16.5.189:4933","state_name":"Up"}
 
alias
 
eg:
»  store --jq=".stores[].store | { id, address, state_alias:.state_name}"
{"id":1,"address":"172.16.5.172:25266","state_alias":"Up"}
{"id":4,"address":"172.16.4.235:25266","state_alias":"Up"}
{"id":7,"address":"172.16.4.237:25266","state_alias":"Up"}
{"id":76,"address":"172.16.5.189:4933","state_alias":"Up"}

eg

{“user”:“stedolan”,“titles”:[“JQ Primer”, “More JQ”]}

表达式

{user, title: .titles[]}

结果

{“user”:“stedolan”, “title”: “JQ Primer”}
{“user”:“stedolan”, “title”: “More JQ”}

找出大于三副本的 region 信息

» region --jq=".regions[].peers|if (length > 3) then . else empty end"
  • 条件和比较
  • if then else end
  • eg: if (.name | length) > 0 then A else B end
  • ==, !=
  • 如果a和b的结果相等(即,如果它们表示等效的JSON文档),则表达式’a == b’将产生’true’,否则将产生’false’。特别是,永远不要认为字符串等于数字

  • !=是“不等于”,并且’a!= b’返回相反的值’a == b’

  • , >=, <=, <

  • 比较运算符>,>=,<=,<返回左边的参数是否大于,大于或等于,小于或等于或小于
  • length
  • 数组的长度是元素的数量
  • 对象的长度是键值对的数量
  • null 的长度为零
  • 结果分解
» region --jq=".regions[].peers|if (length > 3) then . else empty end"
[{"id":5010,"store_id":1},{"id":5011,"store_id":4},{"id":5012,"store_id":7},{"id":5013,"store_id":76,"is_learner":true}]
 
» region --jq=".regions[].peers"
[{"id":29,"store_id":1},{"id":58,"store_id":4},{"id":85,"store_id":7}]
[{"id":37,"store_id":1},{"id":62,"store_id":4},{"id":89,"store_id":7}]
[{"id":4006,"store_id":1},{"id":4007,"store_id":4},{"id":4008,"store_id":7}]
[{"id":4034,"store_id":1},{"id":4035,"store_id":4},{"id":4036,"store_id":7}]
[{"id":17,"store_id":1},{"id":52,"store_id":4},{"id":79,"store_id":7}]
[{"id":23,"store_id":1},{"id":55,"store_id":4},{"id":82,"store_id":7}]
[{"id":4030,"store_id":1},{"id":4031,"store_id":4},{"id":4032,"store_id":7}]
[{"id":5010,"store_id":1},{"id":5011,"store_id":4},{"id":5012,"store_id":7},{"id":5013,"store_id":76,"is_learner":true}]
[{"id":9,"store_id":1},{"id":48,"store_id":4},{"id":69,"store_id":7}]
[{"id":4014,"store_id":1},{"id":4015,"store_id":4},{"id":4016,"store_id":7}]
[{"id":4046,"store_id":1},{"id":4047,"store_id":4},{"id":4048,"store_id":7}]
[{"id":15,"store_id":1},{"id":51,"store_id":4},{"id":71,"store_id":7}]
[{"id":47,"store_id":1},{"id":67,"store_id":4},{"id":75,"store_id":7}]

找出副本数大于 3 的 region 信息,并对输出的 region 信息统计副本数量

» region --jq=".regions[].peers|if (length > 3) then . else empty end | length"
4

找出副本数大于 3 的 region 信息的 ID 信息,并通过数组输出

» region --jq=".regions[].peers|if (length > 3) then . else empty end|map(.store_id)"
[1,4,7,76]

找出副本数大于 3 的 region 信息的 keys 信息

» region --jq=".regions[].peers|if (length > 3) then . else empty end | keys"
[0,1,2,3]
» region --jq=".regions[].peers|if (length > 3) then . else empty end |.[]|keys"
["id","store_id"]
["id","store_id"]
["id","store_id"]
["id","is_learner","store_id"]

  • 运算符和功能
  • 当keys给定一个数组时,它将返回该数组的有效索引:从0到length-1的整数
  • keys当给定对象时,内置函数将其键返回到数组中

找出副本数大于 3 的 region 信息的最大/最小 store_id

» region --jq=".regions[].peers|if (length > 3) then . else empty end|map(.store_id)|max"
76
  • max/min
  • 查找输入数组的最小或最大元素

找出没有 leader 的 region 信息

  • 错误示范
»  region --jq= '.regions[]|select(has("leader")|not)|{id: .id, peer_stores: [.peers[].store_id]}'
region_id should be a number
  • 正确姿势
»  region --jq='.regions[]|select(has("leader")|not)|{id: .id, peer_stores: [.peers[].store_id]}'

注意:= 后面不能有空格,否则报错:region_id should be a number

  • has
  • 内置函数has返回输入对象是否具有给定键,或者输入数组具有给定索引处的元素
  • not
  • 内置函数,是一个过滤器
  • in
  • 内置函数in返回输入键是否在给定对象中,或者输入索引是否对应于给定数组中的元素。即前面返回的结果是否在 in 里面,结果输出为布尔类型

所有在 store1 上有副本并且没有其他 DownPeer 的 region 信息

» region --jq=".regions[] | {id: .id, peer_stores: [.peers[].store_id] | select(length>1 and any(.==1) and all(.!=(30,31)))}"
{"id":24,"peer_stores":[1,32,33]}
  • 输出解读

当大于 1 副本的 region 信息里面有 store_id 为 1 的 store 且同时不包含 30、31时,输出对应的 region 信息

###查找所有 Down 副本数量大于正常副本数量的所有 Region

region --jq='.regions[] | {id: .id, peer_stores: [.peers[].store_id] | select(length as $total | map(if .==(1,240387) then . else empty end) | length>=$total-length) }'
  • 输出解读

首先遍历计算每个 region 的副本情况,在 total 计算之后对前面计算的 region 信息根据 if 判断 region 副本情况,满足就输出并计算 length,最后通过 length>=$total-length 来判断 down 副本信息

参考资料

3赞