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($b)
充當了庫函數,所以為了應對使用者將.
作為實際參數傳遞給它的潛在情況,而對其形式參數採用了變量形式。下面將它保存入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 q(r): . as $pl
| if r < 8 then
$pl[3] as $cl | $cl[] | . as $c
| (r+$c | tostring) as $k0
| (r-$c | tostring) as $k1
| def place:
($k0 | in($pl[1]) | not)
and ($k1 | in($pl[2]) | not);
select(place)
| [$pl[0]+[$c], $pl[1]+{$k0:null},
$pl[2]+{$k1:null}, $cl-[$c]]
| q(r+1)
else
.[0] end;
[[], {}, {}, [range(0; 8)]] | q(0)
| map("abcdefgh"[.:.+1]) | to_entries
| map(.value+(.key+1 | tostring)) | sort;
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"]
在能採用可逆(reversible)賦值和靜態變量的語言比如Icon的實現中,其3個位置列表共有8+15+15 = 38
個元素,而這個存儲傳遞風格實現對3個位置列表進行了zip變換,共有3*8*(1+8)/2 = 108
個元素。
進一步採用隱式編程風格,上述代碼可以寫為:
def queens:
def q(r): . as $pl
| $pl[3] as $cl | $cl[] | . as $c
| (r+$c | tostring) as $k0
| (r-$c | tostring) as $k1
| def place:
($k0 | in($pl[1]) | not)
and ($k1 | in($pl[2]) | not);
select(place)
| [$pl[0]+[$c], $pl[1]+{$k0:null},
$pl[2]+{$k1:null}, $cl-[$c]];
def pipeline(i; n):
q(i) | if i+1 < n then pipeline(i+1; n) end;
[[], {}, {}, [range(0; 8)]] | pipeline(0; 8) | .[0]
| map("abcdefgh"[.:.+1]) | to_entries
| map(.value+(.key+1 | tostring)) | sort;
queens
它還可以採用共遞歸運算recurse()
寫為:
def queens:
def q: . as $pl
| $pl[4] as $r
| $pl[3] as $cl | $cl[] | . as $c
| ($r+$c | tostring) as $k0
| ($r-$c | tostring) as $k1
| def place:
($k0 | in($pl[1]) | not)
and ($k1 | in($pl[2]) | not);
select(place)
| [$pl[0]+[$c], $pl[1]+{$k0:null},
$pl[2]+{$k1:null}, $cl-[$c], $r+1];
[[], {}, {}, [range(0; 8)], 0] | recurse(q)
| select(.[4] == 8) | .[0]
| map("abcdefgh"[.:.+1]) | to_entries
| map(.value+(.key+1 | tostring)) | sort;
queens
def queens(n):
def q: . as $pl
| $pl[4] as $r
| $pl[3] as $cl | $cl[] | . as $c
| ($r+$c | tostring) as $k0
| ($r-$c | tostring) as $k1
| def place:
($k0 | in($pl[1]) | not)
and ($k1 | in($pl[2]) | not);
select(place)
| [$pl[0]+[$c], $pl[1]+{$k0:null},
$pl[2]+{$k1:null}, $cl-[$c], $r+1];
def pipeline(n):
q | if n > 1 then pipeline(n-1) end;
def toletter:
"abcdefghijklmnopqrstuvwxyz"[.:.+1];
def fund_solut(f):
def inverse: . as $xl
| reduce range(0; n) as $i
([]; .+[$xl | index($i)]);
def variants:
[., inverse] | map(., reverse)
| map(., map(n-1-.))
| map(map(toletter) | add);
foreach f as $i
([null, {}]; .[1] as $ml
| ($i | variants) as $nl
| if all($nl[]; in($ml) | not) then
[$i, ($ml | .[$nl[]]=null)]
else
[null, $ml] end;
.[0])
| select (. != null);
fund_solut([[], {}, {}, [range(0; n)], 0]
| pipeline(n) | .[0])
| map(toletter) | to_entries
| map(.value+(.key+1 | tostring)) | sort;
queens(8)
下面演示其執行結果:
$ 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.