"シェルスクリプトの依存関係、ちゃんと把握してますか? depextifyを作った"までAIに書かせる
こんにちは、びしょ~じょです。
gemini-cliでGemini-3-pro-previewが使えることを今更発見し、結局ほとんど使ってなかったserena MCPなどを整備した結果捗るようになった。
仕事でも当然使っているが、もちろんdotfilesなどの盆栽にも利用できる。
そこでoh-my-zshをantidoteに変えてもらったりfdやbatなどのナウい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など) だけでなく、Makefile、Dockerfile(のRUN命令)、 GitHub ActionsのYAML、Taskfile.ymlにも対応しています。 -
賢いフィルタリング:
cdやechoなどのシェル組み込みコマンドや、ls,cpなどのGNU coreutils、その他grep,sedなどの "あって当たり前" なコマンドをデフォルトで除外します。- これにより、
jqやkubectl、awsといった "本当に依存関係として明示すべきコマンド" だけが浮かび上がってきます。
- これにより、
-
Nixフレンドリー:
gomod2nixで管理されており、nix runでシュッと動かせます。
3. 使ってみる
インストールはGoなら go install で一発ですが、Nixユーザならインストールすら不要です。
$ nix run github:nymphium/depextify -- .
例えばこんなシェルスクリプトがあったとします。
#!/bin/bash
echo "Fetching data..."
curl -s https://api.example.com/data | jq .
これを depextify にかけると:
$ depextify test.sh
test.sh
curl
jq
echo は無視され、 curl と jq が抽出されました。
これらはNixの buildInputs や apk add 、 apt-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. 対応フォーマット
個人的に頑張ったのが Dockerfile や Makefile 、GitHub Actionsの解析です。
これらは単なるシェルスクリプトではないですが、中にシェルスクリプトを含んでいます。
depextify はそれらをパースして、中のシェルスクリプト部分だけを取り出し、さらにそれをシェルパーサにかけるということをやっています。
例えば Makefile の行頭タブの後のコマンドや、 Dockerfile の RUN 命令の後のコマンドなどを正しく認識します。
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による記事
そして、これからも……
-
人間注 opamのdepextsより https://opam.ocaml.org/doc/Manual.html#opamfield-depexts ↩
投稿されたコメントはCC BY 4.0ライセンスの下で公開されます。