2 ポイント 投稿者 GN⁺ 2025-01-29 | 1件のコメント | WhatsAppで共有
  • ~/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件のコメント

 
GN⁺ 2025-01-29
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 がその文字列を再び複数の引数に分割してくれるので動作する。とてもかっこよくて便利。

    • macOS ではこの挙動が同じではなく、もどかしい: https://unix.stackexchange.com/a/774145
    • GNU Guile が引数1つの制限を複数行シバンで処理する方法を思い出す。
      #!/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 コードを続けて書ける。
    • env -S はそもそも必要であるべきではなかった。
      シバン行のおかしな空白分割ルールは、古いバグが修正不能な 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

    • 内部がどう動いているかは確認していないが、CLI ツールと Python 3.12 のインストールまで行うなら、エンドユーザーのシステムにコンパイラ、カーネルヘッダー、openssl-devgzipffi のようなライブラリヘッダーが必要で、Python をコンパイルしなければならないのではないかと思う。
      少なくとも ASDF-VM を使ったときは、内部の別の Python インストールツールのためにそういう経験をした。
    • いいね。miseaqua にも便利なツールインストールの概念があるようだ。
  • 面白いシバンの話が出たついでに、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 を実行し、envscryer-prolog "test.pl" を実行する。Prolog 側では 1 行目は /* ... */ コメントなので無視される
    その後シェルは ; の後の exit を実行して、残りの Prolog コードをシェルとして解釈しないようにし、# はシェルに Prolog のコメント終了子 */ を無視させてエラー出力を防ぐ
    自分が作ったものの中で最高であり最悪でもあるかもしれない

    • 同じように Go のソースを実行可能にする方法もある: https://unix.stackexchange.com/questions/162531/shebang-starting-with
      これに加えて、C プログラムも //usr/bin/env sh -c ... 形式の 1 行目を使って実行可能にできる。実行時に make でビルドしてから生成されたバイナリを実行するので、ソースを修正して再実行すると変更後の出力がすぐに得られる
    • 自分で思いついたわけではないが、TCL でもこういうやり方を使う
      #!/bin/sh の次の行に # the next line restarts using tcl \ を置き、exec tclsh "$0" "$@" を実行させることができる
      TCL ではコメント行でも行継続ができる点を利用している
    • これは本当にすごい。Scryer の入出力リダイレクトを少し直せば、bash スクリプトの代替として完璧になりそう
  • Trey Hunner の Lazy self-installing Python scripts with uv という記事に、例付きでさらに詳しい内容がある
    https://treyhunner.com/2024/12/lazy-self-installing-python-scripts-with-uv/

    • 例の中にある「80 個の 0 を出力するスクリプト」は、シェルでも 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
    toolsuv = 'latest' を置き、タスクの run ブロック内で #!/usr/bin/env -S uv run --scriptdependencies = ["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 のように使うことはできるが、対象ファイル基準の相対プロジェクト指定スイッチがあれば、繰り返しを減らせてよさそう

  • uvuvxコマンドランナーとして使う中でいくつか落とし穴には遭遇したものの、たいていはうまく動作し、そういうときは大幅に時間を節約してくれる
    Python開発者であり好奇心旺盛な者として、ホームディレクトリには浅いクローンや一度きりのテスト用ディレクトリが散らばっているが、こうした管理負担を減らしてくれる点は本当に便利
    このアイデアはまねしたい。.pyではない別の拡張子を使っているのか、それとも.pyファイルに実行ビットが立っていてシバンがあるだけで、システムPythonではなくスクリプトとして直接実行できる合図にしているのか気になる

    • この1年ほど、uvをおすすめできるか確かめるために自分で使いながら落とし穴リストを集めてきたが、意外と多くはなかった
      0ではないが、かなり熱心に探している。どんな落とし穴に遭遇したのか分かれば、リストに追加したい
    • 理解したところでは、.pyに実行ビットを付け、記事に出てくるシバン行を付けて使う方式
    • どんな落とし穴があったのか本当に気になる
      Pipenvでは、特定の依存関係がプラットフォームごとに異なるwheelを持つ場合に問題があり、Poetryではうまく動作した
      Pipenvのロックファイルにはロックしたプラットフォームの依存関係だけが含まれていたが、Poetryはすべてのプラットフォームのバリアントをロックファイルに追加する
      この場合にuvがどう動作するのかについてのドキュメントは、まだ見つけられていない
  • 要点はこういうこと
    最近公開されたPEPが、Pythonスクリプトが冒頭コメントで依存関係を宣言する方法を定義しており、uvはその依存関係をスキャンして満たしたうえで、それらのモジュールをインポートできる状態でスクリプトを実行するPythonスクリプトランナー兼パッケージマネージャー
    Pythonスクリプトの1行目コメントであるシバンで、pythonの代わりにuvでファイルを呼び出すようにすると、スクリプトを「実行」するときにこの効果が自動的に得られる
    結果として、設定が必要な依存関係を持つPythonスクリプトもそのまま実行できる

    • ただし、$PATHuvがインストールされている必要がある
      そしてuvは依存関係をインストールしてくれるだろうが、この方式でPythonインタプリタまで自動インストールするのかは明確ではない
    • 結局、システムにuvをインストールし、すべてのスクリプトの先頭に依存関係の行を付けて初めて「そのまま実行」できるということ
      個人的には、従来の方法で環境を作って呼び出すより時間の節約になるとは思えない
      uvが対話型セッションでだけ環境を読み込めるのか、それともスクリプト実行用としてだけ使われるのかもよく分からない
  • TypeScriptが好きなら、Denoでも同じ方式を使える。Bunでもできそうだが、自分では試していない
    #! /usr/bin/env -S deno runのように書き、権限フラグが必要なら#! /usr/bin/env -S deno run --allow-env --allow-read --allow-netのように追加すればよい