2 ポイント 投稿者 GN⁺ 2023-12-10 | 1件のコメント | WhatsAppで共有
  • JC は、さまざまな CLI ツール、ファイル形式、一般的な文字列の出力を JSON に変換し、スクリプトでより簡単にパースできるよう支援するツール
  • パイプ入力を受け取り、STDOUT に JSON を出力し、jc dig example.com のようにコマンドの前に付ける magic syntax もサポート
  • デフォルト出力はパーサーごとの 厳密なスキーマ を使用し、数値、null、ブール値、追加の意味フィールドを変換し、-r または raw=True で前処理前の生 JSON にアクセス可能
  • Python ライブラリとしても利用でき、JSON の代わりに Python の辞書、辞書のリスト、または 遅延 iterable 形式で結果を受け取れる
  • 大規模・長時間実行のパイプライン向けに、JSON Lines/NDJSON を出力する ストリーミングパーサー--slurp--meta-out、ローカルパーサープラグイン、プラットフォーム互換性警告の抑制オプションを提供

JC がすること

  • JC(JSON Convert) は、さまざまな CLI ツール、ファイル形式、一般的な文字列の出力を JSON に変換する
  • 変換された出力は jqjello のようなツールへパイプして追加処理できる
  • 例として、dig example.com | jc --digdig の出力を JSON 配列に変換し、その後 jq -r '.[].answer[].data' で IP アドレスだけを抽出する
  • 同じ処理は jc dig example.com | jq -r '.[].answer[].data' のように magic syntax でも実行可能

CLI と Python ライブラリの使い方

  • 基本的な CLI の使い方は、パイプ入力を受け取って JSON 表現を出力する形式
    • COMMAND | jc [SLICE] [OPTIONS] PARSER
    • cat FILE | jc [SLICE] [OPTIONS] PARSER
    • echo STRING | jc [SLICE] [OPTIONS] PARSER
  • magic syntaxjc [SLICE] [OPTIONS] COMMAND または jc [SLICE] [OPTIONS] /proc/<path-to-procfile> 形式で、コマンドや /proc ファイルの前に jc を付ける
    • コマンド alias と shell builtin はサポートしない
  • Python では jc.parse('dig', cmd_output) のように呼び出す
    • 戻り値は JSON 文字列ではなく、Python の辞書、辞書のリスト、またはストリーミングパーサーの遅延 iterable になる場合がある
  • Python パッケージのドキュメントは help('jc')help('jc.lib')、または オンラインドキュメント で確認できる

出力表現とスキーマ

  • デフォルト表現はパーサーごとの 厳密なスキーマ を使用する
    • 既知の数値は JSON の int または float 値に変換される
    • 既知の None 値は JSON null に変換される
    • 既知のブール値も変換される
    • 一部のパーサーは追加の意味フィールドを付加する
  • 生の前処理 JSON には、CLI の -r オプションまたは Python ライブラリの parse(..., raw=True) でアクセスする
  • 各パーサーのスキーマは、パーサー一覧の横にあるドキュメントリンクで確認できる
  • JSON 出力はデフォルトで compact 形式で、-p オプションで pretty format を使える
  • -y または --yaml-out オプションで YAML 出力も可能

対応パーサーと活用例

  • 対応対象は CLI コマンド、ファイル形式、文字列パーサーで構成される
  • README のパーサー一覧には dig, ls, ping, ps, netstat, ifconfig, csv, xml, yaml, /etc/hosts, /etc/passwd, /proc/, systemctl, git log, jwt, url, semver, x509 関連ファイルなどが含まれる
  • 出力例は、さまざまな入力を JSON 構造に変換する方法を示している
    • arp の出力は、アドレス、ハードウェアタイプ、MAC アドレス、インターフェースのフィールドに変換される
    • CSV ファイルは、ヘッダーをキーにしたオブジェクト配列に変換される
    • /etc/hosts は、IP と hostname 配列に変換される
    • ifconfig は、インターフェース名、MTU、IPv4/IPv6 アドレス、パケット・バイトカウンターなどを含むオブジェクトに変換される
    • ping は、送受信パケット数、損失率、往復時間、応答リストを含むオブジェクトに変換される
  • Ansible では community.general コレクションの filter plugin として利用できる

インストール方法

  • jc は複数の方法でインストールできる
    • pip3 install jc
    • OS パッケージリポジトリ
    • GitHub Releases のアーキテクチャ別バイナリ
  • OS パッケージのインストール例には以下が含まれる
    • Debian/Ubuntu: apt-get install jc
    • Fedora: dnf install jc
    • openSUSE: zypper install jc
    • Arch: pacman -S jc
    • macOS: brew install jc
    • FreeBSD: ports 経由でインストール
    • Ansible filter plugin: ansible-galaxy collection install community.general
    • FortiSOAR connector: FortiSOAR Connector Marketplace からインストール

主なオプション

  • -a / --about: jc とパーサー情報を JSON または YAML で出力する
  • -d / --debug: パース時に問題がある場合にトレースメッセージを出力し、-dd はさらに詳細なデバッグを提供する
  • -h / --help: ヘルプを表示し、jc -h --parser_name でパーサーのドキュメントを確認できる
  • -M / --meta-out: タイムスタンプ、パーサー名、magic command、magic command exit code などの メタデータ を出力に追加する
  • -q / --quiet: パーサーの警告メッセージを抑制し、-qq はストリーミングパーサーのエラーを無視する
  • -s / --slurp: 複数行入力を配列にまとめる
  • -u / --unbuffer: 出力バッファリングを無効化する
  • -B / --bash-comp, -Z / --zsh-comp: Bash または Zsh の completion script を生成する

Slice と Slurp

  • Slice は Python の slicing に似た START:STOP 構文で、入力行の一部だけを処理する
  • たとえばヘッダーとフッターがある表では、jc 1:-1 --asciitable または jc 1:4 --asciitable で中間データだけを JSON に変換できる
  • 正の値と空の slice は最もメモリ効率がよく、負の slice はより多くのメモリを使用する
  • Slurp は、単一行入力を受け取る文字列パーサーで複数項目を一度に配列として出力する機能
    • 例: 1 行に 1 つずつ複数の IP アドレスが入ったファイルを jc --slurp --ip-address で処理する
  • /proc magic syntax は、複数ファイル選択時に自動で slurp をサポートする
    • 複数の /proc ファイルを変換すると、各結果オブジェクトがどのファイルに対応するか分かるよう _file フィールドが追加される
  • --meta-out と slurp を併用すると、result 配列と _jc_meta オブジェクトで包まれた構造が出力される

終了コードとメタデータ

  • jc 内部の致命的なエラーは終了コード 100 を生成し、それ以外では 0 を返す
  • magic syntax 使用時、jc はパース対象プログラムの終了コードを保存し、jc の終了コードに加算する
    • ifconfig1jc0 なら、結合終了コードは 1
    • ifconfig0jc100 なら、結合終了コードは 100
    • 両方がエラーなら、結合終了コードは 101
  • magic syntax で --meta-out または -M を使うと、_jc_meta オブジェクトに magic command 情報と終了コードが含まれる

色と環境変数

  • JC_COLORS 環境変数で key name、keyword、number、string の色を指定できる
    • 形式は JC_COLORS=<keyname_color>,<keyword_color>,<number_color>,<string_color>
    • 色の値は black, red, green, yellow, blue, magenta, cyan, gray, brightblack, brightred, brightgreen, brightyellow, brightblue, brightmagenta, brightcyan, white, default のいずれか
  • NO_COLOR 環境変数を設定すると色付き出力は無効化される
  • -C オプションは NO_COLOR 環境変数と -m オプションより優先され、色付き出力を強制する

ストリーミングパーサー

  • ほとんどのパーサーは STDIN 全体をメモリに読み込んでパースした後、JSON ドキュメントをシリアライズ出力する
  • 一部の ストリーミングパーサー は、入力を受け取るとすぐに行単位で処理し、JSON Lines または NDJSON で出力する
    • 例: ls-s, ping-s
    • ls -lR / のような大きなコマンド出力でも、メモリ使用量を大幅に減らせ、場合によってはより高速に処理できる
  • ストリーミングパーサーは magic syntax と併用できない
  • 長時間実行パイプラインでパースエラーがパイプを壊さないようにするには、-qq または Python の ignore_exceptions=True を使える
    • 成功した行には _jc_meta.success: true が含まれる
    • 失敗した行には _jc_meta.success: falseerrorline フィールドが含まれる
  • パイプ間で OS バッファの影響により出力の表示が遅い場合は、-u で unbuffered output を使える
    • ただし、大量データのストリームでは unbuffered output の方が遅くなることがある
  • Python ではストリーミングパーサーは iterable 入力を受け取り、iterable オブジェクトを返して遅延処理できる

パーサープラグイン

  • ローカル parser plugin はアプリデータディレクトリの jc/jcparsers フォルダーに配置できる
    • Linux/unix: $HOME/.local/share/jc/jcparsers
    • macOS: $HOME/Library/Application Support/jc/jcparsers
    • Windows: $LOCALAPPDATA\\jc\\jc\\jcparsers
  • プラグインは標準的な Python モジュールファイル
  • jc/parsers/foo.py または jc/parsers/foo_s.py をテンプレートとして使える
  • プラグインのファイル名は有効な Python モジュール名でなければならず、文字で始まり、英数字と underscore のみを含められる
  • ローカルプラグインはデフォルトパーサーを override できる

ロケール、タイムゾーン、互換性

  • 最良の結果を得るには、LC_ALLC または en_US.UTF-8 に設定する方法が推奨される
  • 一部の古いシステムでは C locale が UTF-8 エンコーディングをサポートしておらず、UTF-8 出力が ASCII と \\u escape sequence に落ちることがある
  • 一部のパーサーは計算された epoch timestamp フィールドを追加する
    • フィールド名に _utc suffix がなければ、ローカルタイムゾーン基準の naive timestamp とみなされる
    • UTC タイムゾーンがコマンド出力テキストから検出されると timezone aware timestamp となり、キー名に _utc suffix が付く
    • UTC 以外のタイムゾーンは aware timestamp としてはサポートされない
  • 一部のパーサーはプラットフォームごとの出力を変換するため、未対応プラットフォームで実行すると警告を表示する
  • 未対応プラットフォームでも他システムの出力ファイルをパースでき、この場合 -q または Python の quiet=True で警告を抑制できる
  • テスト済みプラットフォームには Centos 7.7、Ubuntu 18.04/20.04、Fedora32、macOS 10.11.6/10.14.6、NixOS、FreeBSD12、Windows 10、Windows 2016 Server、Windows 2019 Server が含まれる

1件のコメント

 
GN⁺ 2023-12-10
Hacker News のコメント
  • FreeBSD では、この問題は libxo によってある程度解決されている:
    $ ps --libxo=json | jq のように構造化された出力を得られる
    完璧ではなく、ls のサポートはあったものの何らかの理由で削除されており、すべてのユーティリティが対応しているわけでもない
    jc は多くのコマンドパーサーを提供する優れた暫定策に見えるが、そもそもパース用に設計されていないテキスト出力をパースするという限界がある
    ユーティリティ群が共通フラグで構造化出力を出す方向にまとまるとよいし、PowerShell のようにデフォルトにするのは Unix/Linux にはやりすぎだとしても、標準の --json フラグがあるだけで大きな前進になる
    https://wiki.freebsd.org/LibXo
    https://reviews.freebsd.org/D13959

    • PowerShell は JSON より一歩進んで、実際の変更可能なオブジェクトをサポートしている
      単に構造化データを渡すのではなく、不透明なオブジェクトをパイプラインに流し、前の段階に戻ってメソッドも呼び出せるものだと理解している: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_methods?view=powershell-7.4
      jclibxo のようなラッパー、そして https://www.nushell.sh/ のような実験的なシェルも良いが、これらは実行可能なメソッドを持つオブジェクトというより、データの受け渡しに焦点を当てている
      構造化データは依然として Unix らしく感じられるし、本物のオブジェクトが必要なら Python や Ruby を起動すべき時だと思う
      シェルがどれほど優れていてプログラミング機能が充実していても、いつシェルスクリプトから本格的なプログラミング言語へ移るべきかを知ることが重要だ
    • 元記事の筆者が書いたブログ記事が、まさにこの内容を扱っている
      jc はこれまでこの役割を埋めるツールとして紹介されており、Unix ツール全般に -j/--json 対応が広く普及するまでの橋渡しとして意図されているように読める
      https://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/
    • SerenityOS でも同様に、/proc 以下の項目が非構造化テキストファイルではなく JSON データを返す
      より構造的な解決策としては、ELF からデータ構造をエクスポートできるようにし、そのデータ構造を端末出力にシリアライズしたうえで、ユーザーが望む JSON や YAML などの形式で出力または処理できるようにする方法があり得る
    • Libxo は理論上は素晴らしいが、アプリケーションが構造を libxo に渡してフォーマットを任せるのではなく、各出力形式ごとのロジックを自前で実装しなければならないように見える
      正確なユーティリティ名は覚えていないが、おそらく iostat で、JSON 出力行を文字列補間でフォーマットしていたところ、特定のフラグ組み合わせで完全に壊れた出力を生成していた
      今は改善されたのか分からないが、間隔オプションがある場合は JSON Lines のような形を期待していた
      使い勝手の面では、PowerShell と kubectl は libxo よりはるかに先を行っていると思う
    • Libxo は FreeBSD の基本システムに入っているが、一般用途にも使える
      https://github.com/Juniper/libxo
      https://libxo.readthedocs.io/en/latest/
  • アイデアは本当に良いが、保守を考えると不安になる
    バージョンやコマンドフラグによる出力の変化などを考慮すると保守地獄のように見えるし、実際の利用は一部のケースではうまくいくだろうが、基本的な例を超えるとすぐに新鮮味が薄れそうだ
    さらに、ツールのオプションに -- を使うのも良くなさそうに見える
    新しいパーサーごとに新しいフラグが必要なら、ヘルプやマニュアルページが何千行にもなり得る

    • これを、コマンドラインユーティリティが徐々に JSON 出力をサポートするようになるにつれて責任範囲が小さくなる**シム(shim)**として見れば、悪くなさそうだ
      あるユーティリティが独自の JSON エクスポートをサポートすることにしたら、その後はこのツールがその機能に委譲すればよい
    • ドキュメントをさらに読み進めると、jc ls のようにコマンドの前に jc を付けて使うこともできる
      --cmd パラメータは変換前にデータを加工できるようにするので、むしろ良いアイデアだ
      たとえば、変換前に一覧を grep したいこともあるだろう
      保守の面でも、基本的な Unix コマンドの出力が大きく変わればこのツールだけでなく多数のスクリプトを壊すため、別のバイナリ更新のせいで思ったほど頻繁に壊れることはなさそうだ
    • 一方で葛藤もある
      よく管理された単一の共用パーサー集は、あちこちに散らばった場当たり的な出力パーサーより優れているが、複雑なことをしようとして直接 JSON 出力が必要になる状況では、パース自体が問題を増やさないでほしい
      これを安心して使うには、結局、自分が使おうとするすべてのものについて追加のテストケースを PR として送ればよさそうだ
      どうせそのテストは自分で書く必要があったはずだから
    • これは協力が必要だ
      必要なツールのパース情報を人々が提出し、利用者が簡単に最新状態を保てるようにするしかない
    • 構造化されていないテキストを構造化されたオブジェクトに変換するうえで高い能力を示した LLM の、より良い活用先の一つだ
  • Nushell はアプローチこそ異なるものの、ほぼ同じ地点である シェルコマンドの構造化データ に到達している
    主にシェル自身がその役割を担う方式である
    http://www.nushell.sh/

    • Nushell には from json 演算があり、JC と実際によく組み合う
      以前、Nushell のよい機能をいくつか紹介する動画を録画しており、19分あたりで jc と組み合わせる話をしている: https://www.youtube.com/watch?v=KF5dtxVsn1E
    • Nushell が最初に発表されてからときどき眺めてはいたが、1〜2か月ほど前になってようやく本質をきちんと理解した
      特定のパターンに合うディレクトリ内のファイルを調べ、更新時刻が互いに10分以内のものを削除するスクリプトを書こうとしていたとき、Nushell がこうした作業に向いていることを思い出した
      少し触ってみたらついに感覚がつかめ、今ではすっかりはまっている
      非構造化データであっても レコードのリスト のような形に変換して処理できれば強力である
  • ある意味では、どこにでもファイルがあるという点は素晴らしく、それが Unix の約束であり、Plan 9 ではさらに拡張された形になっている
    しかし非構造化ファイルだったり、ファイルごとに形式がばらばらだったりすることも同じくらい足かせになる
    awk のようなものだけで nginx のログファイル1つをパースしようとしても面倒になり得る
    大きな欠点の1つは、Linux ユーザー空間で大規模なシステムの書き換えや設計変更を実行するのが難しいことだ
    もっと賢いシェルが欲しいし、ファイルも好きで、横には awk の本も置いてあるが、そろそろ データのパース に本気で改善が必要な時期だと思う
    プログラムがカラー出力をレンダリングするかどうかを自分で判断するように、構造化された出力を出すかどうかもできるとよい

    • 問題の一部は、コマンド出力が ユーザーインターフェイスであり API でもある という点だ
      どんなテキスト UI でも API のように使えるため、人間が読みやすいテキストが優先される
      そのためシェルスクリプティングは、サードパーティ製ブラウザ拡張を作るのに似ている
      見て推測し、その推測をもとにパーサーを急ごしらえし、うまくいくことを願う、というやり方だ
      機械が読むコンテンツ用の第3の標準出力があるとよい
      ターミナルにはデフォルトで出力せず、パイプするときはこの出力がパイプされ、JSONL であり、man ページが契約を明記する方式だ
      そうすれば標準出力は人間向けのままにでき、パースは可能だが脆いものだと理解したうえで行う作業になる
      もちろん完全に後方互換性を壊す発想であり、非現実的に CLI を土台から再発明して現代化するなら、変えたいことのリストは長くなる
    • 半世紀前にかなりよいアイデアが出たのに、多くの人がそれを改善対象ではなく福音のように受け入れているのがもどかしい
      改善を得るのも本当に難しく、Lennart のような人が数十年ものの古い残骸を取り除こうとすると、すぐに騒ぎになる
      JSON もまだ完全ではない
      JSON はそこそこよいアイデアだが、きちんとやるにはデータ型のようなものを表現できる形式が必要だ
      PowerShell により近い方式でなければ、数値を数値として扱い、$a - $b で日付の差分を計算するという驚くべきことはできない
    • より簡単な改善は、すべての CLI ツールにコマンドライン引数で JSON 出力 を実装することだ
      GNU coreutils でこれが実現するとよい
    • PowerShell という選択肢は常にある
      問題は .NET とオブジェクトに深く根を下ろしすぎていて、どのプラットフォームでも既存のネイティブコマンドと統合するのが非常に難しい点だ
    • サーバーログのパースでは、logstash のようなものから機能を切り出せないのが惜しい
      すでに本質的には同じことをしているからだ
      ただ最終目標は結局、上位のツールが価値を認識して直接 構造化された出力 を提供することになりそうだ
  • すばらしい
    妥当な JSON 出力をサポートする CLI ツールを強く支持しており、https://github.com/WireGuard/wireguard-tools/blob/master/contrib/json/wg-json のようなものや PowerShell の |ConvertTo-Json は、管理・監視の自動化で大きな部分を占めている
    ただしここで 妥当な が多くの仕事をしており、現実は現実だ
    LSI/Broadcom StorCLI のコマンドの後ろに J を付ける方式や、PowerShell の COM 隠蔽ラッパーのように、技術的には JSON だが結果が途方もなく複雑だったり役に立たなかったりして、結局「平文出力に正規表現を少しかけよう」というその場しのぎに戻ることが多い
    それでもこれはぜひ見てみるつもりだ
    最初の例である dig 出力のパースが、安定してできることの代表例ならかなり興味深い

  • jc dig example.com が基本構文であるべきだと思う
    dig example.com | jc --dig では、出力をパースするために前のコマンドのフラグやパラメータを後から推測しなければならないからだ

  • すべての出力がオブジェクトである点は、PowerShell で最も好きな部分の1つだ
    bash スクリプトを書かなければならないたびに、それが恋しくなる

  • これをメンテナンスすることにした人に敬意を表する

    • このツールが誤って仮定した部分に実際に直面しながら使うことにした人にも敬意を表する
    • バージョン問題をどう扱うのか気になる
      aws s3 ls | jc --aws=1.2.3 のような形なら悪夢だ
    • よい指摘だ
      Linux または Unix の file プログラムと、それをメンテナンスする英雄たちを思い出す
    • 理論上、plugins のようなものをロードできるなら、たとえば別のシェルコマンドの形にしておけば、メンテナンス作業の一部を プラグイン作者 に渡せる
  • --json オプションを受け付けるモダンな Unix コマンドラインツールの一覧があるのか気になる
    このリポジトリにそうした情報を追加しても有用かもしれない

    • FreeBSD では libxo によって、事実上ほぼすべてが対応している
    • 一覧はないが、ifconfig のモダンな代替ツールである ip は JSON をサポートしている
      lldpctl も対応している
      Ansible はシステムの詳細情報を facts という JSON として提供し、自動化がそれを活用するように設計されている
    • lsblk--json フラグを受け付け、多くの情報を出せる
      lsblk --json --output-all を試してみるとよい
      スクリプトがシステム上のディスクやパーティションを確認する必要があるときに非常に便利
    • Wireshark の CLI 版コンパニオンツールである TShark は、-T json フラグで対応している
    • 意図した答えではないかもしれないが、AWS CLI がある
  • 興味深いプロジェクトだ
    ただ、最初の段階のパーサーとして textfsm のようなものを使うと思っていた
    textfsm はネットワーク機器の CLI 出力をパースするのによく使われている
    https://github.com/google/textfsm