jqがjqlang organizationに移譲され、数名の新たなメンテナーを入れた開発体制に移行してから三か月が経ちました。 私にとってこの三か月はとても濃厚で、これまでのOSS活動の中でも特に大変な期間でした。 itchyny.hatenablog.com github.com
リポジトリの管理権限をいただいてからまずやったことは、既存のissueやPRの整理でした。 500ほどのissueとPRに目を通し、ラベルをつけて、解決済みのものを閉じて、直近で入れたいものを独断でリリースマイルストーンに入れていきました。 この整理がついた頃には他のメンテナの活動も活発になり、私の作ったマイルストーンのissueやPRを確認してくれました。
そして先日、ようやく1.7をリリースしました。 1.6から実に五年弱、一時は開発が完全に止まってしまいプロジェクトの存続を危ぶむ声も上がるような状況からの再起をかけたリリースになりました。 github.com
詳しくはリリースノートを参照していただければ良いと思いますが、この記事でも主な変更点をあげておきましょう。
数値の精度の保持
JSONの中やjqクエリの数値の精度を保持し、比較したりソートしたりできるようになりました。
数値形式のIDやREST APIのナノ秒など、JSONの中の数値が浮動小数点として解釈されたくない場面で役に立ちます。
この機能は2019年10月には実装されてマージされていましたが、正式にリリースがされていなかったため何度も新しいissueが立てられる問題でした。
なお、単項のマイナスや二項演算子、fabs
やround
などの数学的なフィルターが絡むと浮動小数点の精度に落ちます。
$ echo '{"id":100000000000000000}' | jq . { "id": 100000000000000000 } $ jq -n -c '[100000000000000001, 100000000000000003, 100000000000000004, 100000000000000002] | sort' [100000000000000001,100000000000000002,100000000000000003,100000000000000004]
コマンド出力の色
暗いテーマの端末にてnull
の色が見にくいという問題を解消しました。
これまではデフォルトでBold Black (\e[1;30m
) が使われていましたが、Bright Black (\e[0;90m
) に変更しました。
デフォルト色を変更するというのは難しい判断でしたが、設定のない状態で多くの人に快適に使ってもらえるべきと考えて変更しました。 ⭐️
オブジェクトのキーの色をJQ_COLORS
環境変数で設定できるようになりました。
これまで、null
やBoolean、数値や文字列などの色はJQ_COLORS
環境変数によって設定が可能でしたが、オブジェクトのキーだけは設定できないようになっていました。
JSONの型と色付けの設定が一対一対応しているという実装上の制約がありましたが、オブジェクトのキーの色を変更したいという要望は多く、JQ_COLORS
の最後の要素で指定できるように拡張しました。 ⭐️
さらにNO_COLOR
環境変数が設定されていたら色をつけずに出力するようになりました。
多くのコマンドラインツールがサポートしている環境変数であり、色のついた出力を望まないユーザーには便利な機能です。
なお --color-output
(-C
) が指定された場合はそちらが優先されます。 ⭐️
より多くのプラットフォームをサポート
リリースのビルド済み実行ファイルとして、より多くのプラットフォームのためにクロスビルドして公開するようになりました。 以下のアーキテクチャのビルド済み実行ファイルを公開しています。
- Linux:
amd64
,arm64
,armel
,armhf
,i386
,mips
,mips64
,mips64el
,mips64r6
,mips64r6el
,mipsel
,mipsr6
,mipsr6el
,powerpc
,ppc64el
,riscv64
,s390x
- macOS:
amd64
,arm64
- Windows:
i386
,amd64
さらに、GitHub Container Registry ghcr.io/jqlang/jq
にDockerイメージをpushするようになりました。
マルチアーキテクチャ (386
, amd64
, arm64
, mips64le
, ppc64le
, riscv64
, s390x
) のイメージをGitHub Actionsでビルドしています。 ⭐️
github.com
他の人に助言をいただいたのですが、QEMUによるエミュレーションの上でビルドするのは時間がかかるため、ビルド済みのバイナリをscratchイメージにCOPYするだけになっています。 ⭐️ github.com
言語の改善
|= empty
で配列の要素の削除
jqには empty
という特殊なフィルターがあり、空のストリームを表します。
更新代入オペレーター (|=
) の右辺に使うことでオブジェクトのフィールドを削除できますが、配列の要素に使うと意図しない要素を削除してしまうバグがありました。
削除処理を順番に行っていることでインデックスがズレて行ってしまうのが問題でした。
削除対象のインデックスを集めて最後にまとめて削除することで解決しました。 ⭐️
$ jq -n '{foo: 1, bar: 2, baz: 3} | .bar |= empty' { "foo": 1, "baz": 3 } $ jq -n '[range(5)] | (.[] | select(. % 2 == 0)) |= empty' # 1.6 では [1,2,4] [ 1, 3 ]
ただし、普通は del/1
を使うのが良いと思います。
try-catch
のバグ修正
jq 1.6では、try-catch
を更新代入オペレーターの右辺に使うと意図しない挙動をすることがありました。
六件報告されていたこの問題は、エラーハンドリングの内部処理のバグに起因するものでした。
$ jq -n '["true","[]","foo","}{"] | .[] |= try fromjson catch "x"' # 1.6 では ["x","x","x","x"] [ true, [], "x", "x" ] $ jq -n '["1", "2a", "3", 4] | .[] |= tonumber? // .' # 1.6 では ["2a",4] [ 1, 3, 4 ]
オブジェクトのキーとして変数をサポート
オブジェクトのキーに変数をそのまま使えるようになりました。 これまでは括弧が必要でした。
$ jq -n '"test" as $key | {$key:42}' # == {($key):42} { "test": 42 } $ jq -n '"test" as $key | {$key}' # == {key: $key} なので注意 { "key": "test" } $ jq -n '"test" as $key | {$key:$key}' { "test": "test" } $ jq -n '{key:"test"} | {key}' # == {key: .key} { "key": "test" }
ドットの連鎖をサポート
オブジェクトや配列のインデキシング (.["foo"]
, .[0]
) やイテレータ (.[]
, .[]?
) を続けるときに、.
を省略せずに書くことが可能になりました。
$ jq -n '{foo:[]} | .foo.[0], .foo.["foo"]?, .foo.[], .foo.[]?' null # これまでは . を書かない構文のみサポートしていた $ jq -n '{foo:[]} | .foo[0], .foo["foo"]?, .foo[], .foo[]?' null
else節のないif式をサポート
if
式のelse
節を省略できるようになりました。
省略した場合は else .
と同じです。
jqのように常に入力値のある言語での"何もしない"というのは入力値をそのまま返す操作だからです。
$ jq -n '5,15 | if . < 10 then . * 2 end' 10 15
NULセパレータの出力オプション
jqは通常出力を改行区切りで出力します。
これは文字列をクォートせずに出力する--raw-output
(-r
)オプションでも同様ですが、文字列が改行を含みうる場合には他のコマンド (read
, xargs
) に渡すのが安全ではないケースがあります。
この問題を解決するために、--raw-output0
オプションが導入されました。
文字列をクォートせずに出力する上に、セパレータとしてNUL (\x00
)を使います。
jqの出力をread -d ''
やxargs -0
で処理するときに便利です。
$ jq -n --raw-output0 '"a b c", "d\ne\nf"' | xargs -0 printf '[%s]\n' [a b c] [d e f] # 文字列自体がNULを含む場合はエラー $ printf '{"foo":"foo\\u0000bar"}' | ./jq --raw-output0 .foo jq: error (at <stdin>:0): Cannot dump a string containing NUL with --raw-output0 option
新しいビルトインフィルター
以下のビルトインフィルターが追加されました。
pick/1
: 引数で辿った構造を抜き出します。以前のバージョンより{foo}
は{ foo: .foo }
と同じクエリですが、{ foo: { bar: .foo.bar } }
に相当する簡潔な記述方法がありませんでした。今後はpick(.foo.bar)
と簡潔に書けるようになります。さらに、pick(.. | strings | select(contains("GITHUB_")))
といった高度な利用方法もあります。abs/0
: 数値の絶対値を計算します。fabs
やlength
などの同様のフィルターはすでに存在していますが、よりわかりやすい名前とシンプルな実装で導入されました。現在は精度が浮動小数点になりますが、将来的に整数の精度を維持する実装になる可能性があります。debug/1
:debug/0
と同様、標準エラー出力にデバッグ出力を出すフィルターですが、より柔軟に出力を制御できます。例えばdebug("id: \(.id), name: \(.name)")
のように必要なフィールドのみをデバッグすることが可能です。scan/2
:scan/1
と同様、入力文字列から正規表現にマッチする部分文字列を抜き出しますが、正規表現のフラグに対応しています。 ⭐️
ウェブサイトのリデザイン
公式のウェブサイト https://jqlang.github.io/jq/ のデザインをリニューアルしました。 最初にBootstrap 3で作られて以来、誰も手をつけていなかったウェブサイトのデザインを一新して、モダンなサイトに作り替えました。 もちろんダークモードやレスポンシブの対応、svgアイコンの作成なども行いました。 ⭐️
まとめ
五年という歳月を経て、jqの新しいバージョンがリリースされました。 この記事に書いたもの以外にもメモリーリークやセグフォを含む様々なバグが修正されています。 OSS-Fuzzが導入され、スタック保護が有効になるなど、セキュリティ面でも様々な改善が導入されました。 私も⭐️をつけた項目を実装したり、他の人のPRをレビューしたりして、新たなリリースに貢献しました。
jqは現代の開発になくてはならないツールになりました。 私も開発プロセスのあらゆる場面で依存していて、メンテナンスが滞ってセキュリティリスクが高まっても代替を探すのが困難です (gojqに切り替えれば良い説もあります)。 世界中に多くのユーザーがいる中で、バグに見える挙動にも依存しているスクリプトがあることをいつも想像しなくてはいけません。 このように"安定している"ことが期待されるツールを問題なくリリースするのは、かなり緊張感のある作業でした。 二回のRCバージョンを出してユーザーにテストを促しましたが、それでもリリース後に細々としたバグ報告がされています。 新たなメンテナ体制の中で様々なことを学びつつ、この特別なツールの開発を続けていきたいと思っています。