jq语言
来自维基百科,自由的百科全书
jq是领域特定高级的纯函数式编程语言,它采用词法作用域,其中所有JSON值都是常量。jq支持回溯并可管理JSON数据的无限长字串流。jq支持基于名字空间的模块系统,并对闭包有一定支持,尤其是它的函数和泛函表达式可以用作其他函数的参数。
jq与Icon和Haskell编程语言有关。它最初采用Haskell实现[3],随即移植至C语言。gojq是“纯”Go实现。jq还有用Rust实现的叫做jaq的方言[4],它规定了指称语义[5]。
历史
jq由Stephen Dolan创建并在2012年10月发行[6][7] 。它被设计为“针对JSON数据的sed类似者”[8]。在jq版本1.5中增加支持了正则表达式。
针对jq的叫做yq的“包装器”[9],增加支持了YAML、XML和TOML。它首次发行于2017年[10]。
用Go实现的gojq最初发行于2019年[11],gojq显著的扩展jq包括了支持YAML。
用Rust实现的jaq,其项目目标是更快速和更准确的jq实现,仍保持与jq在大多数情况下的兼容性。在2024年3月于其目标中排除了jq的特定高级特征,比如模块、SQL风格算子和给非常大JSON文档的串流解析器[4]。
用jq实现的jqjq,最初发行于2022年。jqjq显著的可以运行自身,拥有REPL并支持eval
。
用法
jq典型的用于命令行,并可以协作于其他命令行实用工具,比如curl。下面的例子展示如何将curl
命令的输出通过管道接转到jq过滤器,从而确定同这个Wikipedia页面关联的范畴名字:
$ URL=$(echo 'jq语言' | jq -rR '@uri "https://zh.wikipedia.org/w/api.php?action=parse&page=\(.)&format=json"')
$ curl -s ${URL} | jq '.parse.categories[]."*"'
这里的流水线产生的输出,由JSON字符串的串流组成,它们是:
"小寫標題"
"CS1英语来源_(en)"
"动态类型编程语言"
"函数式编程语言"
"面向文本编程语言"
"2012年建立的程式語言"
"数据查询语言"
"2012年软件"
上述curl
命令对这个页面使用了MediaWiki API来产生JSON响应。管道(pipe)符号|
允许curl
的输出由jq来访问,它是标准的Unix shell的进程间通信机制[12]。
这里展示的jq过滤器的方法链是如下流水线(pipeline)的简写:
.["parse"] | .["categories"] | .[] | .["*"]
C语言和Go实现二者都提供函数库,使得jq功能可以嵌入到其他应用和编程环境之中。
例如,gojq已经集成于SQLite,故而jq
函数可以在其SQL语句中获得到[13]。这些函数被标记为“确定性的”[14],故而可以被用在CREATE INDEX
命令中[15]。
运算的模态
jq缺省的充当针对JSON输入的“串流编辑器”,非常像被当作多行文本的“串流编辑器”的sed实用工具。但是jq有一些其他运算模态:
- 它可以将来自一个或多个来源的输入当作文本的诸行;
- 它可以将来自特定来源的输入的串流收集到一个JSON阵列之中;
- 它可以使用所谓的“串流解析器”解析其输入,产生针对所有“叶子”路径的
[PATH, VALUE]
阵列的串流。
“串流解析器”(streaming parser),在一个或多个JSON输入太大无法载入内存之时特别有用,因为它需求的内存典型的相当小。例如,对于任意大的JSON对象的阵列,峰值内存需求不比处理最大顶层对象所需要的多出很多。
这些运算模态可以在特定限制下组合起来。
语法和语义
所有JSON值自身是jq中的值,它们从而有在下列表格中展示的类型[16]。gojq和jaq实现将数区分为整数和浮点数。gojq实现支持无界精度整数算术,同于jq采用Haskell的最初实现。
null
是通常表示空值的一个值[17],就像任何其他JSON标量一样;它不是空指针。nan
(对应于NaN)和infinite
(参见IEEE 754),是仅有的两个不是JSON值的jq标量。
jq有特殊语法形式,比如:
EXP as $var | ……
:变量绑定;PATH |= VALUE
和PATH = VALUES
:更新赋值和平凡(plain)赋值;if …… then …… elif …… then …… else …… end
:条件构造;reduce EXP as $var (INIT; UPDATE)
:串流归约;foreach EXP as $var (INIT; UPDATE; EXTRACT)
:输出中间值的串流归约;def func : EXP;
:函数创建;include MOD;
和import MOD as $name;
:模块的包含和导入。
jq中有两种类型的符号,可称为“变量”的值绑定和函数。二者都是词法作用域的,表达式只能提及其左侧即前面最近的定义的符号,但是函数可以提及自身来创建递归函数。
jq是面向JSON的编程语言,使用|
符号来连接过滤器形成流水线。例如:
$ echo '[1,2]' | jq 'add'
3
$ jq -n '[1,2] | add'
3
这里jq内的JSON阵列[1,2]
是求值为阵列的一个jq过滤器。
尽管类似于Unix流水线,jq流水线允许将到来数据,如同并行的发送到在|
右手端的多于一个接收者。例如,程序add/length
将计算阵列中数的平均,故而:
$ jq -n '[1,2] | add/length'
1.5
$ jq -nc '[1,2] | [length, add, add/length]'
[2,3,1.5]
单独的点号.
可以充任占位符(placeholder)或通配符,例如:
$ jq -nc '1 | [., .]'
[1,1]
$ jq -n '2 | pow(.; .)'
4
在jq中采用隐式编程风格,阶乘可以通过共递归运算recurse()
定义为[0,1] | recurse([first+1, last*(first+1)]) | last
,下面是其使用例子:
$ echo '0 1 2 6' | jq -c '[limit(.+1; [0,1] | recurse([first+1, last*(first+1)]) | last)]'
[1]
[1,1]
[1,1,2]
[1,1,2,6,24,120,720]
$ jq -n 'first([0,1] | recurse([first+1, last*(first+1)]) | last)'
1
$ jq -n '[0,1,2,6][] | nth(.; [0,1] | recurse([first+1, last*(first+1)]) | last)'
1
1
2
720
$ jq -nc '[0,1,2,6] | map(nth(.; [0,1] | recurse([first+1, last*(first+1)]) | last))'
[1,1,2,720]
斐波那契数列可以通过共递归运算recurse()
定义为[0,1] | recurse([last, add]) | first
,下面是其使用例子:
$ echo '0 1 2 6' | jq -c '[limit(.+1; [0,1] | recurse([last, add]) | first)]'
[0]
[0,1]
[0,1,1]
[0,1,1,2,3,5,8]
$ jq -n 'first([0,1] | recurse([last, add]) | first)'
0
$ jq -n '[0,1,2,6][] | nth(.; [0,1] | recurse([last, add]) | first)'
0
1
1
8
$ jq -nc '[0,1,2,6] | map(nth(.; [0,1] | recurse([last, add]) | first))'
[0,1,1,8]
这种无限列表不可以不加限定的使用,也不可以对其进行last()
运算。下面将其定义为新的命名过滤器:
def fac: nth(.; [0,1] | recurse([first+1, last*(first+1)]) | last);
def fib: nth(.; [0,1] | recurse([last, add]) | first);
共递归运算recurse()
可以设置递推条件:
$ jq -nc '[0 | recurse(.+1; . < 0)]'
[0]
$ jq -nc '[0 | recurse(select(. < 0) | .+1)]'
[0]
$ jq -nc '[0 | recurse(.+1; . <= 6)]'
[0,1,2,3,4,5,6]
$ jq -nc '[0 | recurse(select(. < 6) | .+1)]'
[0,1,2,3,4,5,6]
下面的例子展示如何定义参数化的命名过滤器,它格式化从2到36含二者的任何底数的整数:
def tobase(b):
def digit: "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[.:.+1];
def mod: .%b;
def div: (.-mod)/b;
def place_values: recurse(select(. >= b) | div) | mod;
select(2 <= b and b <= 36)
| [place_values | digit] | reverse | add;
将其保存入tobase.jq
文件,接着使用这个函数:
$ echo '15 16' | jq 'include "./tobase"; tobase(16)'
"F"
"10"
从M
是在最高位的进位可知:M
为1
,S
为8
或9
,O
为0
,代码展示了基于range()
和.[]
的生成器:
def send_more_money:
def choose(m; n; k): . as $used
| ([range(m; n+1)]-[$used[]])[] | $used+{(k):.};
def num: reduce .[] as $i (0; 10*. + $i);
{"m":1,"o":0}
| choose(8; 9; "s") | choose(2; 9; "e")
| choose(2; 9; "n") | choose(2; 9; "d")
| choose(2; 9; "r") | choose(2; 9; "y")
| select(([.s,.e,.n,.d] | num)
+ ([.m,.o,.r,.e] | num)
==([.m,.o,.n,.e,.y] | num))
| [.s,.e,.n,.d,"+",.m,.o,.r,.e,"=",.m,.o,.n,.e,.y];
send_more_money
将上述代码保存入send_more_money.jq
文件,接着使用这个函数得到这个谜题仅有的一个解:
$ jq -nc -f ./send_more_money.jq
[9,5,6,7,"+",1,0,8,5,"=",1,0,6,5,2]
解析表达式文法
在jq和解析表达式文法(PEG)形式化之间有密切关联[18]。这种关联源于下列表格中展示的PEG七个基本运算与jq构造之间的等价性。
PEG运算名字 | PEG表示法 | jq运算或def |
---|---|---|
序列 | e1 e2
|
e1 | e2
|
有序选择 | e1 / e2
|
e1 // e2
|
零或多个 | e*
|
def star(E): (E | star(E)) // .;
|
一或多个 | e+
|
def plus(E): E | (plus(E) // .);
|
可选 | e?
|
def optional(E): E // .;
|
与断言 | &e
|
def amp(E): . as $in | E | $in;
|
非断言 | !e
|
def neg(E): select([E] == []);
|
八皇后问题例子
回溯法求解八皇后问题,采用函数式编程风格的条件表达式和递归函数可以写为:
def queens:
def place(r; c; pl):
all(pl[0][]; . != c)
and all(pl[1][]; . != r+c)
and all(pl[2][]; . != r-c);
def q(r): . as $pl
| if r < 8 then
range(0; 8)
| . as $c | select(place(r; $c; $pl))
| [$pl[0]+[.], $pl[1]+[r+.], $pl[2]+[r-.]]
| q(r+1)
else
.[0] | map("abcdefgh"[.:.+1])
| to_entries | map(.value+(.key+1 | tostring))
| sort end;
[[],[],[]] | q(0);
queens
解1
| ||||||||||||||||||||||||||||||||||||||||||||
解2
|
将这段代码保存入queens.jq
文件中,下面演示其执行结果并提取其92
个解中的前两个解:
$ jq -nc -f ./queens.jq | wc -l
92
$ jq -nc -f ./queens.jq | sed -n '1,2p'
["a1","b7","c5","d8","e2","f4","g6","h3"]
["a1","b7","c4","d6","e8","f2","g5","h3"]
这个存储传递风格实现的3种位置列表,共有3*9 = 27
个列表和3*9*(0+8)/2 = 108
个元素,而在能采用可逆(reversible)赋值和静态变量的语言比如Icon的实现中,其3个位置列表共有8+15+15 = 38
个元素。
进一步采用隐式编程风格,上述代码可以写为:
def queens:
def q(r): . as $pl
| range(0; 8)
| def place: . as $c
| all($pl[0][]; . != $c)
and all($pl[1][]; . != r+$c)
and all($pl[2][]; . != r-$c);
select(place)
| [$pl[0]+[.], $pl[1]+[r+.], $pl[2]+[r-.]];
def pipeline(i; n):
q(i) | if i+1 < n then pipeline(i+1; n) end;
[[],[],[]] | pipeline(0; 8) | .[0]
| map("abcdefgh"[.:.+1])
| to_entries | map(.value+(.key+1 | tostring)) | sort;
queens
它还可以采用共递归运算recurse()
写为:
def queens:
def q: . as $pl
| $pl[3] as $r | select($r < 8) | range(0; 8)
| def place: . as $c
| all($pl[0][]; . != $c)
and all($pl[1][]; . != $r+$c)
and all($pl[2][]; . != $r-$c);
select(place)
| [$pl[0]+[.], $pl[1]+[$r+.], $pl[2]+[$r-.], $r+1];
[[],[],[],0] | recurse(q) | select(.[3] == 8) | .[0]
| map("abcdefgh"[.:.+1])
| to_entries | map(.value+(.key+1 | tostring)) | sort;
queens
def queens:
def q: . as $pl
| $pl[3] as $r | range(0; 8)
| def place: . as $c
| all($pl[0][]; . != $c)
and all($pl[1][]; . != $r+$c)
and all($pl[2][]; . != $r-$c);
select(place)
| [$pl[0]+[.], $pl[1]+[$r+.], $pl[2]+[$r-.], $r+1];
def fund_solut(f):
def inverse:
to_entries | map([.value, .key])
| sort | map(.[1]);
def variants:
[., inverse] | map(., reverse) | map(., map(7-.))
| map(map(tostring) | add);
reduce f as $i
([[],{}]; .[1] as $ml | ($i | variants) as $nl
| if all($nl[]; in($ml) | not) then
[.[0]+[$i], ($ml | .[$nl[]]=null)] end)
| .[0][];
def pipeline(n):
q | if n > 1 then pipeline(n-1) end;
fund_solut([[],[],[],0] | pipeline(8) | .[0])
| map("abcdefgh"[.:.+1])
| to_entries | map(.value+(.key+1 | tostring)) | sort;
queens
下面演示其执行结果:
$ jq -nc -f ./queens.jq | wc -l
12
$ jq -nc -f ./queens.jq
["a1","b7","c5","d8","e2","f4","g6","h3"]
["a1","b7","c4","d6","e8","f2","g5","h3"]
["a6","b1","c5","d2","e8","f3","g7","h4"]
["a4","b1","c5","d8","e2","f7","g3","h6"]
["a5","b1","c8","d4","e2","f7","g3","h6"]
["a3","b1","c7","d5","e8","f2","g4","h6"]
["a5","b1","c4","d6","e8","f2","g7","h3"]
["a7","b1","c3","d8","e6","f4","g2","h5"]
["a5","b1","c8","d6","e3","f7","g2","h4"]
["a5","b3","c1","d7","e2","f8","g6","h4"]
["a5","b7","c1","d4","e2","f8","g6","h3"]
["a6","b3","c1","d8","e4","f2","g7","h5"]
注释
参考书目
引用
外部链接
Wikiwand - on
Seamless Wikipedia browsing. On steroids.