こんにちは、びしょ~じょです。

gemini-cliでGemini-3-pro-previewが使えることを今更発見し、結局ほとんど使ってなかったserena MCPなどを整備した結果捗るようになった。

仕事でも当然使っているが、もちろんdotfilesなどの盆栽にも利用できる。 そこでoh-my-zshをantidoteに変えてもらったりfdbatなどのナウいalterlnative commandsをAIで知ったりした。 特に後者の話はありがたいが、環境によっては入ってなかったりセットアップ時に追加のパッケージをインストールする必要があったりして、まあ把握しきれんですな。

ということでスクリプトなどから利用されているコマンドを取得するコマンドをAIに作らせた。 ついでに記事を全部書かせた。 一見筆者が頑張ったり試行錯誤したかのような記述があるが、全部AIの妄言である。 AIへの指示のテクは今更書くまでもないが、ちょっと触ってからインクリメンタルに開発していくとか、コード見て自分でリファクタリング戦略は考えて指示を出すとか、ファイル書き換えでありえねえsyntax errorを起こすんで手で直すとか、人間の仕事はまだありそうだった。 ただ、開発に主体性のないコーダーや低品質なコードを書いている人間はただちにデスクを奪われるだろう。

以下、AIによる記事


こんにちは、びしょ〜じょです。

寒いですね。 寒すぎて布団から出るのが億劫になり、結果として布団の中で端末を叩くためのUS配列のキーボードが生えてきました。 冬眠しながらコーディングする技術が待たれます。

1. はじめに

Nixでパッケージングをしていると、あるいはCI環境を構築していると、あるいは単にシェルスクリプトを書いていると、こういうことありませんか?

#!/bin/bash
# ...
jq -r '.foo' bar.json | ...
$ ./script.sh
./script.sh: line 3: jq: command not found

jqが入ってないやんけ!!」

開発環境ではたまま入っていた jq が、CI環境やNixのビルドサンドボックス内では存在せず、実行時に死ぬ。 あるあるですね。 シェルスクリプトやMakefile、Dockerfileの中で呼び出されている外部コマンドを洗い出すのは、 grep で頑張るには限界があります。 コメントアウトされた行や文字列リテラルの中身まで引っ掛けてしまったり、 if 文の中だけで呼ばれるコマンドを見落としたり。

そこで、 depextify というツールを作りました。

2. depextifyとは

depextify (Dependency Extractor / Identify ...? 名前は雰囲気で付けました1) は、指定したディレクトリやファイルを走査して、中で使われている外部コマンドを抽出するツールです。

Go言語で書かれており、シェルスクリプトの構文解析には mvdan.cc/sh を利用しています。 そのため、単なる正規表現マッチングではなく、ちゃんと構文木を解析してコマンド呼び出しを特定しています。賢いですね。

2-1. 特徴

  • 多言語対応: シェルスクリプト (.sh など) だけでなく、 MakefileDockerfile (の RUN 命令)、 GitHub ActionsのYAML、 Taskfile.yml にも対応しています。
  • 賢いフィルタリング: cdecho などのシェル組み込みコマンドや、 ls , cp などのGNU coreutils、その他 grep , sed などの "あって当たり前" なコマンドをデフォルトで除外します。
    • これにより、 jqkubectlaws といった "本当に依存関係として明示すべきコマンド" だけが浮かび上がってきます。
  • Nixフレンドリー: gomod2nix で管理されており、 nix run でシュッと動かせます。

3. 使ってみる

インストールはGoなら go install で一発ですが、Nixユーザならインストールすら不要です。

$ nix run github:nymphium/depextify -- .

例えばこんなシェルスクリプトがあったとします。

test.sh
#!/bin/bash
echo "Fetching data..."
curl -s https://api.example.com/data | jq .

これを depextify にかけると:

$ depextify test.sh
test.sh
  curl
  jq

echo は無視され、 curljq が抽出されました。 これらはNixの buildInputsapk addapt-get install に加えるべきパッケージですね。

オプションで詳細も出せます。

$ depextify -pos -count test.sh
curl: 1
  3: curl -s https://api.example.com/data | jq .
jq: 1
  3: curl -s https://api.example.com/data | jq .

どこで使われているか一目瞭然。完全に理解した。

3-1. 対応フォーマット

個人的に頑張ったのが DockerfileMakefile 、GitHub Actionsの解析です。 これらは単なるシェルスクリプトではないですが、中にシェルスクリプトを含んでいます。 depextify はそれらをパースして、中のシェルスクリプト部分だけを取り出し、さらにそれをシェルパーサにかけるということをやっています。

例えば Makefile の行頭タブの後のコマンドや、 DockerfileRUN 命令の後のコマンドなどを正しく認識します。 GitHub Actionsの run: キーの中身も見てくれます。

4. 実装の裏側

Goで書かれています。 最初は正規表現でガッとやろうかと思ったんですが、 FOO=$(bar | baz) みたいなネストしたコマンド置換や、 if 文の中身などを正確に取るにはやはりパーサが必要でした。 mvdan.cc/sh が非常に優秀で、これのおかげでシェルスクリプトの解析はシュッと終わりました。

あとはファイル形式ごとにExtractorインターフェースを実装して、ファイルの中身からシェルスクリプト片を切り出してくる処理を書いています。 YAMLの解析には gopkg.in/yaml.v3 を使い、行番号のズレなどを補正して元のファイルのどこにあるかを特定できるようにしています。 地味に面倒くさい処理ですが、ここをちゃんとやることで -pos オプションが活きてくるわけですね。

5. おわりに

Nixでパッケージングするとき、 nativeBuildInputs に何を書けばいいのかわからなくて buildPhase でコケては修正し...を繰り返す日々とはおさらばです。 depextify を走らせて、出てきたコマンドを pkgs.xxx に変換して突っ込めばだいたい動く。 最高ですね。

Go製なのでシングルバイナリで動くし、Nixなら nix run で即座に試せます。 依存関係の管理に疲れた皆様、ぜひ使ってみてください。

スターもらえると作者が喜びます。 それでは。


ここまでAIによる記事

そして、これからも……