基础表达式(Basic filters)是 jq 提供的基本过滤器,用来访问 JSON 对象中的属性,主要有以下几种:
- '.' 符号。单独的一个'.'符号用来表示对作为表达式输入的整个 JSON 对象的引用。
- JSON 对象操作。jq 提供两种基本表达式用来访问 JSON 对象的属性:'.'和'.?'。正常情况下,这两个表达式的行为相同:都是访问对象属性,如果 JSON 对象不包含指定的属性则返回 null。区别在于,当输入不是 JSON 对象或数组时,第一个表达式会抛出异常。第二个表达式无任何输出。
- 数组操作。jq 提供三种基础表达式来操作数组:
- 迭代器操作('.[]'). 该表达式的输入可以是数组或者 JSON 对象。输出的是基于数组元素或者 JSON 对象属性值的 iterator。
- 访问特定元素的操作('.[index]'或'.[attributename]')。用来访问数组元素或者 JSON 对象的属性值。输出是单个值
- 数组切片操作('.[startindex:endindex]'),其行为类似于 python 语 言中数组切片操作。
- 表达式操作(','和 '|')。表达式操作是用来关联多个基础表达式。其中逗号表示对同一个输入应用多个表达式。管道符表示将前一个表达式的输出用作后一个表达式的输入。当前一个表达式产生的结果是迭代器时,会将迭代器中的每一个值用作后一个表达式的输入从而形成新的表达式。例如'.[]|.+1', 在这个表达式中,第一个子表达式'.[]'在输入数组上构建迭代器,第二个子表达式则在迭代器的每个元素上加 1。
jq 内部支持的数据类型有:数字,字符串,数组和对象(object)。并且在这些数据类型的基础上, jq 提供了一些基本的操作符来实现一些基本的运算和数据操作。列举如下:
- 数学运算。对于数字类型,jq 实现了基本的加减乘除(/)和求余(%)运算。对于除法运算,jq 最多支持 16 位小数。
- 字符串操作。jq 提供字符串的连接操作(运算符为'+',例如:"tom "+"jerry"结果为"tom jerry"),字符串的复制操作(例如:'a'*3 结果为'aaa'),以及字符串分割操作(将字符串按照指定的分割符分成数组,例如"sas"/"s"的结果为["","a",""],而"sas"/"a"的结果为["s","s"]。
- 数组操作。jq 提供两种数组运算:并集('+')运算,结果数组中包含参与运算的数组的所有元素。差集运算('-'),例如:有数组 a,b, a-b 的结果为所有在 a 中且不包含在 b 中的元素组成的数组。
- 对象操作。jq 实现了两个 JSON 对象的合并操作(merge)。当两个参与运算的对象包含相同的属性时则保留运算符右侧对象的属性值。有两种合并运算符:'+'和''。所不同的是,运算符'+'只做顶层属性的合并,运算符''则是递归合并。例如:有对象 a={"a":{"b":1}}, b={"a":{"c":2}},a+b 的结果为{"a":{"c":2}},而 a*b 的结果为{"a":{"b":1,"c":2}}
- 比较操作:jq 内部支持的比较操作符有==, !=,>,>=,<=和<。其中,'=='的规则和 javascript 中的恒等('===')类似,只有两个操作数的类型和值均相同时其结果才是 true。
- 逻辑运算符: and/or/not。在 jq 逻辑运算中,除了 false 和 null 外,其余的任何值都等同于 true。
- 默认操作符('//'), 表达式'a//b'表示当表达式 a 的值不是 false 或 null 时,a//b 等于 a,否则等于 b。
- 格式化高亮展示
$ echo -ne '{"a":1, "b":2}' | jq
# 相当于
$ echo -ne '{"a":1, "b":2}' | jq '.'
{
"a": 1,
"b": 2
}
- 返回某一个字段的值
$ echo -ne '{"a":1, "b":2}' | jq '.a'
1
- 返回数组的值
$ echo -ne '{"a":[1,2]}' | jq '.a[0]'
1
$ echo -ne '{"a":1, "b":2, "c":{"c1":[11,22,33], "c2":[123,456,789]}}' | jq 'keys'
[
"a",
"b",
"c"
]
这里看到只针对顶级的进行处理,如果想打印子对象的keys,可以这样:
$ echo -ne '{"a":1, "b":2, "c":{"c1":[11,22,33], "c2":[123,456,789]}}' | jq '.c | keys'
[
"c1",
"c2"
]
{
"a": 1,
"b": 2,
"c": {
"c1": [
11, 22, 33
],
"c2": [
123, 456, 789
]
}
}
$ jq '.c | "\(.c1[0]):\(.c2[0])"'
"11:123"
直接拼凑就行了:
echo -ne '{"a":"1", "b":"2"}' | jq '.a + " " + .b'
"1 2"
# 可以结合 --raw-output使用
echo -ne '{"a":"1", "b":"2"}' | jq '(.a|tostring) + " " + (.b|tostring)' --raw-output
$ jq ".key1" < file.json > out.txt
$ echo '{"a":"1234"}' | jq ".a" --raw-output
$ echo '{"a":"1234"}' | jq ".a | length"
4
$ echo '{"a":"1234"}' | jq ".a, (.a | length)"
"1234"
4
$ echo -e '{"a":"1234"}\n{"a":"1"}' | jq "select((.a | length)>3) | .a"
"1234"
-c 参数
$ echo -e '{"a":"1234",\n "b": 1}' | jq -c
{"a":"1234","b":1}
$ echo '{"a":"1234"}' | jq ". | length"
1
$ echo '{"lxdns.com":{"name":"网宿 CDN","link":"https://cn.chinacache.com/"},"cloudflare.net":{"name":"Cloudflare","link":"https://www.cloudflare.com"}}' | jq 'keys[] as $k | select(.[$k].name=="网宿 CDN") | $k'
"lxdns.com"
echo -e '{"a":1}\n{"a":2,"error":"yes"}\n{"a":3}' | jq '. | select(.error == null) | .a'
1
3
echo -ne '{"a":"1", "b":"2"}' | jq 'join("\t")' --raw-output
echo -ne '{"a":"1", "b":"2"}' | jq '.a + "\t" + .b' --raw-output
1 2
echo -ne '{"a":"1", "level":"1"}\n{"a":"2", "level":"2"}\n{"a":"3", "level":"3"}' | jq 'select((.level|tonumber)==2) | .a'
"2"
也可以多条查询
echo -ne '{"a":"1", "level":"1"}\n{"a":"2", "level":"2"}\n{"a":"3", "level":"3"}' | jq 'select((.level|tonumber)==1 or (.level|tonumber)==3) | .a'
"1"
"3"
echo -ne '{"a":"123"}\n{"a":"234"}\n{"a":"345"}' | jq 'select(.a|contains("2"))' -c
{"a":"123"}
{"a":"234"}
在select里面用not就好了:
echo -ne '{"a":"123"}\n{"a":"234"}\n{"a":"345"}' | jq 'select(.a | contains("2") | not )' -c
{"a":"345"}
echo -ne '{"a":"234"}\n{"a":"123"}\n{"a":"345"}' | jq -s -c 'sort_by(.a)[]'
{"a":"123"}
{"a":"234"}
{"a":"345"}
echo -ne '[{"a":1},{"a":2}]' | jq '.[].a'
1
2
echo '{"a":[1,2,3,4],"b":[2,4,6,8,10]}' | jq '.a[] as $x | .b[] | select($x == .)' -c
2
4
echo -n '{"a":1}\n{"b":2}' | jq -s -c 'reduce .[] as $item({}; .+$item)'
{"a":1,"b":2}
❯ echo '{"ip":"1.1.1.1","a":1}\n{"ip":"1.1.1.1","b":2}\n{"ip":"2.2.2.2","c":3}' | jq -s 'group_by(.ip)'
[
[
{
"ip": "1.1.1.1",
"a": 1
},
{
"ip": "1.1.1.1",
"b": 2
}
],
[
{
"ip": "2.2.2.2",
"c": 3
}
]
]
echo '{"ip":"1.1.1.1","a":1}\n{"ip":"1.1.1.1","b":2}\n{"ip":"2.2.2.2","c":3}' | jq -s -c 'group_by(.ip)| map({ ip: (.[0].ip), fields: [.[]|del(.ip)] }) | .[]'
{"ip":"1.1.1.1","fields":[{"a":1},{"b":2}]}
{"ip":"2.2.2.2","fields":[{"c":3}]}
echo '{"ip":"1.1.1.1","a":1}\n{"ip":"1.1.1.1","b":2}\n{"ip":"2.2.2.2","c":3}' | jq -s -c 'group_by(.ip)| map({ ip: (.[0].ip) } + ([.[]|del(.ip)] | reduce .[] as $item({}; .+$item)) ) | .[]'
{"ip":"1.1.1.1","a":1,"b":2}
{"ip":"2.2.2.2","c":3}
# 保留一个字段
echo '{"ip":"1.1.1.1","a":1}' | jq -c '{ip}'
{"ip":"1.1.1.1"}
# 保留多个字段
echo '{"ip":"1.1.1.1","a":1}' | jq -c '{ip,a}'
{"ip":"1.1.1.1","a":1}
# 删除一个字段
echo '{"ip":"1.1.1.1","a":1}' | jq -c 'del(.a)'
{"ip":"1.1.1.1"}
# 删除多个字段
echo '{"ip":"1.1.1.1","a":1}' | jq -c 'del(.a,.ip)'
{}
用引号括起来:
echo '{"a.b":1}' | jq '."a.b"'
1
echo -n '{"a":"1","b":"2","c":"3"}\n{"a":"4","b":"5","c":"6"}' | jq --slurp --compact-output --raw-output '(map(keys) | add | unique) as $cols | map(. as $row | $cols | map($row[.])) as $rows | $cols, $rows[] | @csv'
"a","b","c"
"1","2","3"
"4","5","6"
用引号括起来:
echo '{"a":1}' | jq -c '{"b":."a"}'
{"b":1}
echo '{"a":1,"b":[["ip","os","updated_at"],["1.1.1.1","","2021-03-24 18:55:22"],["2.2.2.2","","2021-03-24 18:55:22"]]}' | jq --compact-output '. | .+{b:(reduce .b[1:][] as $item([]; .+[$item[0]]) )}'
{"a":1,"b":["1.1.1.1","2.2.2.2"]}
echo '{"a1":[1,2,3,4],"a2":[3,4,5]}' | jq -c '.a1more=.a1-.a2, .a2more=.a2-.a1, .a1a2eq=(.a1-(.a1-.a2))'
{"a1":[1,2,3,4],"a2":[3,4,5],"a1more":[1,2]}
{"a1":[1,2,3,4],"a2":[3,4,5],"a2more":[5]}
{"a1":[1,2,3,4],"a2":[3,4,5],"a1a2eq":[3,4]}
好像不能,不过可以变通的用一个数组或者object来实现效果:
echo '{"a1":[1,2,3,4],"a2":[3,4,5]}' | jq -c 'def fun(a):a[0],a[1]; fun([.a1,.a2])'
[1,2,3,4]
[3,4,5]
echo '{"a1":[1,2,3,4],"a2":[3,4,5]}' | jq -c 'with_entries(.value=1)'
{"a1":1,"a2":1}
echo '{"a1":[1,2,3,4],"a2":[3,4,5]}' | jq -c 'with_entries(.key=(if .key=="a1" then "b" else .key end ))'
{"b":[1,2,3,4],"a2":[3,4,5]}
echo '{"a":[["1.1.1.1:80",80,"1.1.1.1"],["a.com:443",443,"2.2.2.2"]]}' | jq '.a | reduce .[] as $item ([]; if ($item[0] | contains($item[2]) | not) then .+=[$item[0]] else . end)'
[
"a.com:443"
]
echo '["1.1.1.1","1.1.1.1:81","http://1.1.1.1:82","https://1.1.1.1:83","[fe80::8407:ad05:f6be:90ad]", "https://[fe80::8407:ad05:f6be:90ad]:8443"]' | jq 'def extracthost(host): host|split("://")|last|(if (.|contains("]")) then .|split("]")[0:-1]|join("")+"]" else split(":")|first end); reduce .[] as $item ([]; .+=[extracthost($item)])'
[
"1.1.1.1",
"1.1.1.1",
"1.1.1.1",
"1.1.1.1",
"[fe80::8407:ad05:f6be:90ad]",
"[fe80::8407:ad05:f6be:90ad]"
]
默认情况下jq是有buffer的,如果想要实时,可以用--unbuffered参数:
fofa random --fixUrl --size 100 --fields host --sleep 1 | jq -r .host --unbuffered | go run ./main.go
echo '{
"aggregations": {
"query_strings": {
"buckets": [
{
"key": "aaa",
"user_id_count": {
"value": 1
}
},
{
"key": "bbb",
"user_id_count": {
"value": 2
}
}
]
}
}
}' | jq '.aggregations.query_strings.buckets | sort_by(.user_id_count.value)'
场景如下:go test生成的out文件,用gocov生成了json格式。想要用jq找出没有调用的函数列表(找到Statements下所有Reached计数都为0的语句对应的函数名称)。 可以这么写:
cat coverage.json | jq '[.Packages[].Functions[] | select(.Statements | all(.Reached == 0)) | .Name] | unique'
# 打印pkg
cat coverage.json | jq '.Packages[] | .Name as $pkg | .Functions[] | select(.Statements | all(.Reached == 0)) | {package: $pkg, function: .Name} '
# 排除@开头
cat coverage.json | jq '.Packages[] | .Name as $pkg | .Functions[] | select(.Name | startswith("@") | not) | select(.Statements | all(.Reached == 0)) | {package: $pkg, function: .Name} '
# 函数调用百分比统计
cat coverage.json | jq '
[.Packages[].Functions[] | select(.Name | startswith("@") | not)] |
{
total_functions: length,
called_functions: map(select(.Statements | any(.Reached > 0))) | length,
uncalled_functions: map(select(.Statements | all(.Reached == 0))) | length
} |
. + {
coverage_percentage: (.called_functions / .total_functions * 100 | round | tostring + "%")
}
'