~/bin/ に自動化スクリプトを置いて実行する運用では、uv とインラインスクリプトメタデータを使うことで Python スクリプトをより手軽に扱える
~/bin が PATH に入っていれば、shebang を #!/usr/bin/env -S uv run --script に指定することで、ファイル名をコマンドのように実行できる
- この設定により、シェルがまず
uv run --script を実行し、現在のスクリプトファイルを引数として渡す
- uv は shebang 行を無視したうえで、残りの内容を通常の Python ファイル として実行する
chmod a+x {filname} で実行権限を付与すれば、依存関係を自動処理するシンプルな Python コマンドラインスクリプトとして使える
shebangとしてuvを実行する
- 作業の自動化のために
~/bin/ ディレクトリに複数のスクリプトを作って使う
~/bin が PATH に含まれていれば、スクリプトをファイル名だけで直接実行できる
- このとき使う shebang は次のとおり
#!/usr/bin/env -S uv run --script
実行の流れと権限設定
- 上の shebang はコマンドラインで
uv run --script を実行し、スクリプトファイルを引数として渡す
- uv は shebang を無視したあと、ファイルの残りの部分を通常の Python コードとして実行する
- 実行前にはスクリプトに実行権限を付与する必要がある
chmod a+x {filname}
- 設定を終えれば、依存関係を自動処理するシンプルな Python コマンドラインスクリプトをすぐに実行できる
1件のコメント
Hacker News のコメント
今日 env -S を初めて知って、記事中のシバンを見た瞬間に「Linux では動かないはず。シバンは引数を1つしか渡せないから」と思った。
たとえば
#!/usr/bin/env -S uv run --scriptで始まるfoo.pyを実行すると、OS は実際には/usr/bin/env '-S uv run --script' foo.pyのように、シバン行全体を1つの引数として渡す。ところが
envの-Sがその文字列を再び複数の引数に分割してくれるので動作する。とてもかっこよくて便利。#!/usr/bin/guile \の次の行に-e main -sを置き、!#で閉じると/usr/bin/guile -e main -s filenameのようになる。なぜわざわざこうしたのかは気になる。おそらく
env -Sが最近追加されたものだったか、関心のあるすべてのプラットフォームで使えなかったのだろう。https://www.gnu.org/software/guile/manual/html_node/The-Meta-Switch.html
makeに似ているが設定に YAML を使う Go CLI の tusk と一緒に、この方法は使いやすい。#!/usr/bin/env -s go run github.com/rliebz/tusk@latest -fをシバンに置き、インタプリタにはinterpreter: go run mvdan.cc/sh/v3/cmd/gosh@latest -sのように Go シェルの gosh を使う。こうすると Go さえインストールされていれば、どのアーキテクチャでも実行可能な CLI になる。
たとえば nix-shell は
#!/usr/bin/env nix-shellの次の行に#!nix-shell --pure -i runghc ./default.nixを置いて依存関係と引数を指定でき、その後に Haskell コードを続けて書ける。シバン行のおかしな空白分割ルールは、古いバグが修正不能な Unix の欠点として定着してしまったものだ。
上のような小細工を使うたびに、1980年代に30分だけかけていれば、その後何年もの苛立ちを避けられたのにと思う。シバン行は最初から
/bin/shのように分割されるべきだった。シバンではないが、最近 uv をインストールツールのように使っている。
複雑な依存関係を持つ Python CLI ツールのパッケージングと配布が難しいので、2つの方法で活用した。
1つは
curl | shのインストールスクリプトをコピーし、末尾にuv tool install --python python3.12 my-toolを追加して、ユーザーが1行で CLI をインストールできるようにする方法。もう1つは、uvだけを依存関係に持つ小さな PyPI の「インストーラ」パッケージを作り、uv tool installを実行させる方法だ。どちらの方法でも、CLI ツール、Python 3.12、すべての Python 依存関係を隔離された環境にインストールする。ユーザーは仮想環境を管理する必要がなく、
curl | sh方式では Python がインストールされている必要すらない。それ以降、ユーザーが複雑な依存関係を壊して発生する GitHub の issue は大幅に減った。
詳細とコードへのリンク: https://aider.chat/2025/01/15/uv.html
openssl-dev、gzip、ffiのようなライブラリヘッダーが必要で、Python をコンパイルしなければならないのではないかと思う。少なくとも ASDF-VM を使ったときは、内部の別の Python インストールツールのためにそういう経験をした。
miseとaquaにも便利なツールインストールの概念があるようだ。面白いシバンの話が出たついでに、Prolog の
.plファイルをシェルから実行しようとして、こんなものを作った/*usr/bin/env scryer-prolog "$0" "$@" ; exit #*//**/コメントはあるが、#コメントや#!シバンを直接サポートしていないスクリプト言語なら、たいてい動きそう元のコメント: https://github.com/mthom/scryer-prolog/issues/2170#issuecomment-1821713993
このファイルは有効なシェルファイルであると同時に、有効な Prolog ファイルでもある。シェルとして実行すると、1 行目がグロブパターン
/*usr/bin/envを探して/usr/bin/envを実行し、envがscryer-prolog "test.pl"を実行する。Prolog 側では 1 行目は/* ... */コメントなので無視されるその後シェルは
;の後のexitを実行して、残りの Prolog コードをシェルとして解釈しないようにし、#はシェルに Prolog のコメント終了子*/を無視させてエラー出力を防ぐ自分が作ったものの中で最高であり最悪でもあるかもしれない
これに加えて、C プログラムも
//usr/bin/env sh -c ...形式の 1 行目を使って実行可能にできる。実行時にmakeでビルドしてから生成されたバイナリを実行するので、ソースを修正して再実行すると変更後の出力がすぐに得られる#!/bin/shの次の行に# the next line restarts using tcl \を置き、exec tclsh "$0" "$@"を実行させることができるTCL ではコメント行でも行継続ができる点を利用している
Trey Hunner の Lazy self-installing Python scripts with uv という記事に、例付きでさらに詳しい内容がある
https://treyhunner.com/2024/12/lazy-self-installing-python-scripts-with-uv/
printf %.1s 0{1..80} $'\n'やfor i in {1..80}; do echo -n 0; done; echoで可能ffmpeg の例も
ffmpeg -i in.mp4 -c:v copy -filter:a volumedetect -pass 1 -f null /dev/null && ffmpeg -i in.mp4 -c:v copy -filter:a "loudnorm" -pass 2 out.mp4と何が違うのかよく分からない正直、Python のほうが複雑に見える
小さな作業には mise(mise-en-place)と一緒にこのコツを使える: https://mise.jdx.dev/tasks/toml-tasks.html#shell-shebang
toolsにuv = 'latest'を置き、タスクのrunブロック内で#!/usr/bin/env -S uv run --scriptとdependencies = ["requests<3", "rich"]を宣言してから Python コードを入れる方式16 日前にも関連する記事と議論があった: Uv's killer feature is making ad-hoc environments easy https://news.ycombinator.com/item?id=42676432(502 ポイント、コメント 417 件)
覚えておく価値のあるコツ
最近 Python のエイリアスを
uv run pythonに変えたが、かなり快適。.venvなどを管理する必要がなく、システムインストールやグローバル/ユーザーインストールの問題も気にしなくてよく、Debian では特に助かるプロジェクトや環境の中で REPL を実行するときも、
activateなしで呼び出せるので考えることが減るただし
.pyを uv で直接呼び出す場合、プロジェクト/環境ファイルのパスは現在の作業ディレクトリ基準で決まり、.pyファイルの位置基準ではない点が欠点明示的なスイッチ
--projectがあり、uv run --project /script.pyのように使うことはできるが、対象ファイル基準の相対プロジェクト指定スイッチがあれば、繰り返しを減らせてよさそうuvやuvxをコマンドランナーとして使う中でいくつか落とし穴には遭遇したものの、たいていはうまく動作し、そういうときは大幅に時間を節約してくれるPython開発者であり好奇心旺盛な者として、ホームディレクトリには浅いクローンや一度きりのテスト用ディレクトリが散らばっているが、こうした管理負担を減らしてくれる点は本当に便利
このアイデアはまねしたい。
.pyではない別の拡張子を使っているのか、それとも.pyファイルに実行ビットが立っていてシバンがあるだけで、システムPythonではなくスクリプトとして直接実行できる合図にしているのか気になる0ではないが、かなり熱心に探している。どんな落とし穴に遭遇したのか分かれば、リストに追加したい
.pyに実行ビットを付け、記事に出てくるシバン行を付けて使う方式Pipenvでは、特定の依存関係がプラットフォームごとに異なるwheelを持つ場合に問題があり、Poetryではうまく動作した
Pipenvのロックファイルにはロックしたプラットフォームの依存関係だけが含まれていたが、Poetryはすべてのプラットフォームのバリアントをロックファイルに追加する
この場合にuvがどう動作するのかについてのドキュメントは、まだ見つけられていない
要点はこういうこと
最近公開されたPEPが、Pythonスクリプトが冒頭コメントで依存関係を宣言する方法を定義しており、uvはその依存関係をスキャンして満たしたうえで、それらのモジュールをインポートできる状態でスクリプトを実行するPythonスクリプトランナー兼パッケージマネージャー
Pythonスクリプトの1行目コメントであるシバンで、
pythonの代わりにuvでファイルを呼び出すようにすると、スクリプトを「実行」するときにこの効果が自動的に得られる結果として、設定が必要な依存関係を持つPythonスクリプトもそのまま実行できる
$PATHにuvがインストールされている必要があるそしてuvは依存関係をインストールしてくれるだろうが、この方式でPythonインタプリタまで自動インストールするのかは明確ではない
個人的には、従来の方法で環境を作って呼び出すより時間の節約になるとは思えない
uvが対話型セッションでだけ環境を読み込めるのか、それともスクリプト実行用としてだけ使われるのかもよく分からない
TypeScriptが好きなら、Denoでも同じ方式を使える。Bunでもできそうだが、自分では試していない
#! /usr/bin/env -S deno runのように書き、権限フラグが必要なら#! /usr/bin/env -S deno run --allow-env --allow-read --allow-netのように追加すればよい