[HN Gopher] Using uv as your shebang line
___________________________________________________________________
Using uv as your shebang line
Author : Einenlum
Score : 210 points
Date : 2025-01-28 17:35 UTC (5 hours ago)
(HTM) web link (akrabat.com)
(TXT) w3m dump (akrabat.com)
| thecodemonkey wrote:
| What is uv?
| woodruffw wrote:
| https://github.com/astral-sh/uv
| avidiax wrote:
| Seems that it interprets a special comment block, then
| automatically creates a python venv and gathers dependencies.
| As the post[1] says, it obviates "faffing" with venv and pip to
| run a script.
|
| https://akrabat.com/defining-python-dependencies-at-the-top-...
| benreesman wrote:
| It's a package manager for Python (among other things) that is
| simultaneously correct and performant. It's getting a lot of
| attention these days.
|
| I highly recommend it over all conceivable alternatives.
| IgorPartola wrote:
| How likely is this to be another npm/yarn situation? I have
| used a number of Python tools that in the long run turned out
| to be less portable and less usable than plain Pip and
| virtualenv. I do use pyenv for a personal setup but not for
| any production use.
| d0mine wrote:
| You can use uv and get the benefits today: it combines the
| functionality from several tools (including pyenv) and the
| performance is such that it is transformative.
| baq wrote:
| as if it was so simple. don't forget about pnpm.
|
| it's the same for uv/pip/poetry except uv is so much better
| than the alternatives there isn't any contest. pip is the
| safe default (which doesn't even work out of the box on
| debian derivatives, which is half the issue) and uv is...
| just default.
| einpoklum wrote:
| The idea is as follows:
|
| * A recently-published PEP specificies how Python scripts can
| declare dependencies in opening comments.
|
| * uv is a Python script-runner/package manager which scans for
| those dependencies, arranges for them to be met, and runs the
| script with those dependency modules available for importing
|
| * If, in a Python script, you use the first line comment - the
| shebang line - to have the file invoked using uv rather than
| python itself, you'll get that effect automagically when
| "running" the Python script
|
| The result: Python scripts which require setup of dependencies
| will now "just run".
| bityard wrote:
| (If you have `uv` installed in your $PATH.)
|
| Also, uv will install the dependencies, but I am not clear on
| whether or not it will automatically install a Python
| interpreter for you using this method.
| kjkjadksj wrote:
| Will now "just run" after you satisfied the dependency of
| installing uv on your system and prepending these dependency
| lines to all your scripts, that is.
|
| Imo this saves you no time over just constructing and calling
| environments the old way. I'm not sure either whether uv lets
| you load the env only in an interactive session or if its just
| for script running.
| einpoklum wrote:
| I'll first day I'm not a proponent of uv, I just read the
| post and tried to clarify what it says...
|
| but I think what it saves you is the trouble of setting up a
| virtual environment the "old" way. So, it's about making
| things easier, not faster.
| fastasucan wrote:
| Ofcourse it does, after you have installed uv and prepending
| the dependencies you never have to do it again. Calling
| environments etc have to be done every time. You also have to
| keep these env around.
| vunderba wrote:
| I've been meaning to look into uv. However, since I make pretty
| heavy use of conda for my pythons scripts, I'll usually add a
| bash function that handles conda activation / code execution to
| my .zshrc file. function transcribe() { #
| get the active conda environment active_env=$(conda info
| | grep "active environment" | awk '{print $4}') if
| [[ "$active_env" != "speechrec-env" ]]; then conda
| activate speechrec-env fi python
| /users/vunderba/dev/transcriber/transcribe.py "$@" }
|
| It's handy because if it'll always point to the most current
| version of my script.
| kjkjadksj wrote:
| What seems to suck about the uvx way of doing things is that
| your script and dependencies are now in one document. It just
| seems so much more useful for so many reasons to keep these
| things discrete in my mind. Especially when we are talking
| about uvx which is something probably only a fraction of python
| devs actually use vs a built in paradigm of the language. Yes,
| conda is not built in but it might as well be with the amount
| of influence it has on python development and downstream tools
| to manage conda envs (e.g. mamba or snakemake).
| babel_ wrote:
| That's not the uvx way? Dependencies at the top of a script
| was outlined in PEP 723, which uv/uvx added support for. Not
| everything is a "project", some files are just one-and-done
| scripts which should not have to carry the burden of
| project/environment management, but absolutely should still
| be able to make use of dependencies. The "uvx way" of doing
| it just means that it doesn't have to pollute the global/user
| install, and can even be isolated into a separate instance.
|
| Besides, not everyone uses conda, and it would be quite a
| stretch to say it "might as well" be a built-in compared to,
| well, the actual built in, pip! Plus, uv works quite nicely
| as "just a pip replacement", which is how I started with it,
| so it aligns quite well to the actual built-in paradigm of
| the language.
| simonw wrote:
| That's a misundersanding of uv - the inline script
| dependencies feature is strictly optional. You're encouraged
| to use pyproject.toml as a default instead of that for most
| projects. https://docs.astral.sh/uv/guides/projects/
| krick wrote:
| Wow, this is super obvious (so obvious that it wouldn't occur to
| me to write a post to brag about it), yet it somehow didn't occur
| to me. Very cool.
|
| Not so long ago I started pip-installing my scripts to keep
| things tidy. It seems now I can regress back to chaos again...
| nomel wrote:
| My favorite way to pass small scripts to non software
| colleagues: try: import package
| except ImportError: import pip
| print("Installing package...") pip.main(['install',
| 'package'] import package
|
| :D
| hv42 wrote:
| You can use this trick with mise (mise-en-place) for small tasks:
| https://mise.jdx.dev/tasks/toml-tasks.html#shell-shebang
| [tools] uv = 'latest' [tasks.python_uv_task]
| run = """ #!/usr/bin/env -S uv run --script # ///
| script # dependencies = ["requests<3", "rich"] # ///
| import requests # your code here """
| kseistrup wrote:
| Something similar is shown for uv here:
| https://simonwillison.net/2024/Aug/21/usrbinenv-uv-run/
| threecheese wrote:
| I e experienced a few "gotchas" using uv (or uvx) as a command
| runner, but when it works (which is most of the time) it's a big
| time saver. As a Python dev and curious person my homedir is
| littered with shallow clones and one-off directories for testing
| some package or another, and not having to manage this overhead
| is really useful.
|
| OP, you have a great idea and I'm stealing it. Do you use some
| non-.py suffix, or is seeing the exec bit at on a .py file enough
| of a signal for you to know that you can just run the file as a
| script (and it won't use the system Python)?
| przemub wrote:
| From what I understand, he uses .py with exec bit and the
| shebang line as in the article.
| BiteCode_dev wrote:
| I've been collecting gotchas from using uv myself over the last
| year to assess whether to recommend it or not and I found
| surprisingly few. Not zero, but I'm looking pretty hard.
|
| Would love to hear what gotchas to find so I can add that to
| the list.
| achierius wrote:
| Is the list public? I'm trying to figure out whether it's
| worth taking the plunge.
| zahlman wrote:
| I really don't understand how it is that other people frequently
| write one-off scripts that nevertheless need to run in an
| isolated environment (as opposed to a sandbox that contains a few
| common workhorse packages like NumPy or Requests).
|
| Of course I have plenty of separate environments set up, but
| that's because I'm _developing_ things I hope to share with
| others, so I need to keep close track of their specific
| dependencies (so I can specify them in package metadata). But
| even this stuff would generally work with a wide range of
| versions for those dependencies. I 'd imagine this is equally
| true of small, one-off, personal automation scripts.
|
| But, sure, if you have this problem, that's a great way to use
| the shebang line. (You can do similarly with Pipx, and I expect
| you'll be able to with Paper as well, although I might push that
| sort of thing into an optional add-on.)
| traverseda wrote:
| It's when you need to give the script to someone else.
| imtringued wrote:
| They don't care about the isolated environment. They care about
| having the right dependencies available to the script.
|
| Competing languages like Java don't need or even have the
| concept of isolated environments. It's an illogical concept.
| You have a class path and that is about it.
| orf wrote:
| A virtual environment is just a project-specific class path,
| but for Python.
| tcptomato wrote:
| Which also installs a complete JVM in it.
| zahlman wrote:
| Not really; it can just symlink the base executable and
| make a few folders and scripts. $
| python -m venv --without-pip test-venv && du -sh test-
| venv 56K test-venv
| dragonwriter wrote:
| Conceptually, a venv has its own executable.
|
| Physically, that can just be a symlink to the system
| python (or another version that is still potentially
| shared with multiple venvs.)
| simonw wrote:
| That's one of the great things about uv: it uses clever
| symlink tricks to avoid installing copies of things, so
| it's wildly fast.
| zahlman wrote:
| >They care about having the right dependencies available to
| the script.
|
| Right; my point is that I'd expect one-off scripts like this
| to keep relying on the same few standard dependencies which
| would already have been set up in an environment somewhere.
|
| >You have a class path and that is about it.
|
| A Python venv pretty much is just a layer on top of the
| existing "class path" mechanism (sys.path) to point it at the
| right place automatically (reproducing the basic folder
| layout relative to a symlinked executable, so that sys.path
| ends up with the right paths and so that there's a dedicated
| folder for installing the dependencies). While it's ugly and
| not recommended, you can work with the PYTHONPATH environment
| variable, or even modify sys.path at runtime.
| SAI_Peregrinus wrote:
| The isolated environment is required because most Linux
| systems also depend upon Python, and can have dependency
| conflicts if you install development dependencies that
| conflict with system dependencies.
| dkarl wrote:
| The isolated environment can matter when different scripts
| that you use depend on libraries that have conflicting
| dependency requirements.
|
| > Competing languages like Java don't need or even have the
| concept of isolated environments
|
| The nice thing about scripts is that you can quickly add new
| capabilities when you need them. They are always under
| development. To get the same thing in Java, you need a
| project directory with a build file, which gives you an
| isolated build environment.
| simonw wrote:
| I think it's just good hygiene.
|
| If my weird one-off ad-hoc script needs numpy I can bake the
| exact tested version into an inline script dependency and run
| it with "uv run" and never have to think about it ever again.
|
| If I don't do that, there's a good chance that in a year I'll
| upgrade numpy for some other project and now my ad-hoc script
| will break.
|
| (Numpy is actually a great example here, lots of code I try to
| run turns out to need numpy 1.0 because it hasn't yet been
| upgraded to work with the new 2.0 - here's a recent example
| https://simonwillison.net/2024/Dec/24/modernbert/ )
| traverseda wrote:
| You can also do the same thing with the nix package manager and
| pretty much any language or dependency. Like if you wanted a
| portable bash-script with imagemagick #!
| /usr/bin/env nix-shell #! nix-shell -i bash -p bash
| imagemagick
|
| Handy for passing quick scripts around. Nothing is quite as
| portable as a single file like this.
| IshKebab wrote:
| > Nothing is quite as portable as a single file like this.
|
| Well... a file that doesn't require Nix is probably a fair bit
| more portable!
| traverseda wrote:
| Sure, an appimage, the UV example above
| nikkindev wrote:
| "Lazy self-installing Python scripts with uv"[1] article from
| Trey Hunner has more details with examples
|
| [1] https://treyhunner.com/2024/12/lazy-self-installing-
| python-s...
| godelski wrote:
| > For example, here's a script I use to print out 80 zeroes (or
| a specific number of zeroes)
|
| Also... # Print 80 0's and flush printf
| %.1s 0{1..80} $'\n' # Alternatively for i in
| {1..80}; do echo -n 0; done; echo
|
| For the ffmpeg example is this any different from
| 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
|
| The python seems more complicated tbh
| kseistrup wrote:
| See also https://news.ycombinator.com/item?id=42198256 from
| November 2024 (40 comments).
| PhilippGille wrote:
| From 68 days ago:
|
| "/usr/bin/env -S uv run":
| https://news.ycombinator.com/item?id=42198256
|
| 125 points, 45 comments
| d0mine wrote:
| to drop .py extension, you need to add `--script`
| babel_ wrote:
| Now that's a trick I should remember!
|
| I recently switched over my python aliases to `uv run python` and
| it's been really quite pleasant, without needing to manage
| `.venv`s and the rest. No fuss about system installs for python
| or anything else either, which resolves the old global/user
| install problem (and is a boon on Debian). Also means you can
| invoke the REPL within a project/environment without any
| `activate`, which saves needing to think about it.
|
| Only downside for calling a .py directly with uv is the cwd
| relative pathing to project/environment files, rather than
| relative to the .py file itself. There is an explicit switch for
| it, `--project`, which at least is not much of an ask (`uv run
| --project <path> <path>/script.py`), though a target relative
| project switch would be appreciated, to at least avoid the
| repetition.
| tiltowait wrote:
| I've seen uv a lot recently. Is there any major benefit over
| poetry?
| __mharrison__ wrote:
| Speed! You won't go back
| dave4420 wrote:
| Uv will manage python versions for you.
| beaugunderson wrote:
| - great lock update behavior by default (though --no-update
| became the default in poetry 2.0)
|
| - speed, as others have said (5 minutes - 70 seconds for our
| docker build)
|
| - much easier (and faster) virtual environment management than
| poetry, should you want a venv
|
| - manages the python version, including new GIL-less variants
| if you want to try them
|
| - script dependencies (include requirements as a comment in a
| script and it will install and use them)
| xboxnolifes wrote:
| I've been using uv as a drop-in replacement for poetry. My
| little usage of it so far has led me to view it as just a
| better poetry. Does the same things I wanted from poetry, but
| faster and I haven't hit the dependency resolve issues I hit
| with poetry in the past (tbf, I think these got fixed in poetry
| since).
| rav wrote:
| Oh wow, today I learned about env -S - when I saw the shebang
| line in the article, I immediately thought "that doesn't work on
| Linux, shebang lines can only pass a single argument". Basically,
| running foo.py starting with #!/usr/bin/env -S
| uv run --script
|
| causes the OS run really run env with only two arguments, namely
| the shebang line as one argument and the script's filename as the
| second argument, i.e.: /usr/bin/env '-S uv run
| --script' foo.py
|
| However, the -S flag of env causes env to split everything back
| into separate arguments! Very cool, very useful.
| sangeeth96 wrote:
| It's frustrating this is not the same behavior on macOS:
| https://unix.stackexchange.com/a/774145
| silverwind wrote:
| `brew install coreutils` and update your `PATH`.
| sangeeth96 wrote:
| I'm aware of this package for getting other utilities but:
|
| 1. I'm worried about this conflicting/causing other
| programs to fail if I set it on PATH. 2. This probably
| doesn't fix the shebang parsing issue I mentioned since
| it's an OS thing. Let me know if that's not the case.
| andreineculau wrote:
| You've got nothing to worry
|
| Been doing it for more than a decade and yet to get in
| trouble. Not one issue. Doing it consistently for my
| teams as we decrease cognitive load (developing on macs
| but targeting unix). Others would confirm
| https://news.ycombinator.com/item?id=17943202
|
| Basically software will either use absolute paths i.e.
| wants to use your OS version for a dependency like grep,
| or will use whatever grep is in your $PATH and stick to
| safe invocations regardless if it's BSD/GNU or if it's
| version x or y
| rav wrote:
| It seems to me that macOS has env -S as well, but the shebang
| parsing is different. The result is that shebang lines using
| env -S are portable if they don't contain any quotes or other
| characters. The reason is that, running env -S 'echo a b c'
| has the same behavior as running env -S 'echo' 'a' 'b' 'c' -
| so simple command lines like the one with uv are still
| portable, regardless of whether the OS splits on space
| (macOS) or not (Linux).
| fjarlq wrote:
| This is true. For example, the following shebang/uv header
| works on both macOS and Linux:
| #!/usr/bin/env -S uv --quiet run --script # ///
| script # requires-python = ">=3.13" #
| dependencies = [ # "python-dateutil", # ]
| # /// # # [python script that needs dateutil]
| viraptor wrote:
| If the wrapper itself cooperates, you can also embed more
| information in the following lines. nix-shell for example
| allows installing dependencies and any parameters with:
| #!/usr/bin/env nix-shell #!nix-shell --pure -i runghc
| ./default.nix ... Any Haskell code follows
| __float wrote:
| Uv supports this:
| https://docs.astral.sh/uv/guides/scripts/#declaring-
| script-d...
|
| (Though, this is more general than uv for Python script deps:
| https://packaging.python.org/en/latest/specifications/inline.
| ..)
| IshKebab wrote:
| Yeah unfortunately support for that is kind of spotty, so don't
| do this in any scripts you want to work everywhere.
| mingus88 wrote:
| Yeah, my first reaction was cool, what's uv
|
| Oh, yet another python dependency tool. I have used a handful
| of them, and they keep coming
|
| I guess no work is important enough until it gets a super
| fast CLI written in the language du jour and installed by
| piping curl into sh
| kernelbugs wrote:
| I believe parent comment was about `env -S` not being
| portable rather than `uv` being portable.
|
| I'll say, I am as pessimistic as the next person about new
| ways to do X just to be hip. But as someone who works on
| many different Python projects day to day (from fully
| fledged services, to a set of lambdas with shared internal
| libraries, to CI scripts, to local tooling needing to run
| on developer laptops) - I've found uv to be particularly
| free of many sharp edges of other solutions (poetry,
| pipenv, pyenv, etc).
|
| I think the fact that the uv tool itself is not written in
| Python actually solves a number of real problems around
| bootstrapping and dependency management for the tool that
| is meant to be a dependency manager.
| IshKebab wrote:
| > Oh, yet another python dependency tool. I have used a
| handful of them, and they keep coming
|
| Yeah that's my opinion of all the other Python dependency
| tools, but uv is the real deal. It's fast, well designed,
| it's actually a drop-in replacement for pip and it actually
| works.
|
| > I guess no work is important enough until it gets a super
| fast CLI written in the language du jour and installed by
| piping curl into sh
|
| Yeah it's pretty nice that it's written in Rust so it's
| fast and reliable, and piping curl into sh makes it easy to
| install. Huge upgrade compared to the rest of Python
| tooling which is slow, janky and hard to install. Seriously
| the official way to install a recent Python on Linux is to
| build it from source!
|
| It's a shame curl | bash is the best option we have but it
| does at least work reliably. Maybe one day the Linux
| community will come up with something better.
| alt187 wrote:
| It's, er, "funny" how people used to make fun of `curl |
| sh` because of how lame it was, and now you have it
| everywhere because Rust decided that this should be the
| install.
| baq wrote:
| uv is _the_ tool, finally. We 've been waiting for two
| decades and it really does basically everything right, no
| ifs or buts. You can scratch off the 'yet another' part.
| gkfasdfasdf wrote:
| It would be nice if uv had something like uvx but for
| scripts...uvs maybe? Then you could put it as a single arg to
| env and it would work everywhere.
| tiberriver256 wrote:
| uv makes Python actually usable. Freaking love that tool.
| 7thpower wrote:
| Does anyone have any intro to UV guides they found helpful?
|
| I haven't dove into uv as it just hasn't been at the top of my
| list, but I will probably take the plunge soon.
|
| I gave it an initial try thinking it might function as a drop in,
| but didn't find any great guides, and just haven't been back yet.
| But we're ~8% into 2025 so it's time.
| picafrost wrote:
| Astral are making great tools. There are many points of friction
| in the Python world and Astral is making them seem obvious to see
| and obvious to solve. That is hard work.
| dcre wrote:
| For the TypeScript enjoyers: you can do the same with Deno (and
| I'm sure Bun too, though I haven't done it). #!
| /usr/bin/env -S deno run
|
| You can add permission flags like this: #!
| /usr/bin/env -S deno run --allow-env --allow-read --allow-net
| IshKebab wrote:
| I love Deno and I use this, but I really wish they would fix
| this dumb bug:
|
| https://github.com/denoland/deno/issues/16466
| sebmellen wrote:
| I really wish they would allow you to connect to "unsafe" TLS
| endpoints.
|
| https://github.com/denoland/deno/issues/6427#issuecomment-65.
| ..
| mkl wrote:
| Related article and discussion from 16 days ago: Uv's killer
| feature is making ad-hoc environments easy
| https://news.ycombinator.com/item?id=42676432 (502 points, 417
| comments)
| TrueDuality wrote:
| One gotcha that annoys me when do this... The file still needs to
| end in .py which I generally don't like for the bins and
| utilities in my path. Everyone using these shebangs either
| doesn't hit this or never mentions it. Entirely a stylistic
| personal preference but still makes me a sad lady.
| puzzledobserver wrote:
| Are you sure about this?
|
| I just wrote a script without a .py extension, and it seemed to
| work fine.
| d0mine wrote:
| `--script` has been added to fix it https://github.com/astral-
| sh/uv/issues/6360#issuecomment-245...
| infogulch wrote:
| Speaking of funny shebangs, I came up with this to shell execute
| prolog .pl files, but it should work for any scripting language
| that has /**/-comments but doesn't support #-comments or #!
| shebangs specifically: /*usr/bin/env scryer-
| prolog "$0" "$@" ; exit #*/
|
| From my original comment https://github.com/mthom/scryer-
| prolog/issues/2170#issuecomm... :
|
| > The way this works is that this test.pl is both a valid shell
| file and prolog file. When executing as shell the first line
| finds and executes /usr/bin/env after searching for the glob
| pattern /*usr/bin/env. env then executes scryer-prolog "test.pl"
| which runs the prolog file as a module and halts; of course
| prolog ignores the first line as a comment /* ... */. Then the
| shell continues and executes the next command after ; which is
| exit which stops execution so the rest of the file (which is not
| valid shell) is ignored. The # makes the shell evaluator ignore
| prolog comment closer */ to prevent it from printing an error.
|
| This may be the best and worst thing I have ever created.
| upghost wrote:
| Oh my god this is amazing. We need to fix some of the IO
| redirect in Scryer and then this will be a perfect replacement
| for bash scripts <3
|
| You rock!
| Levitating wrote:
| Ruby's bundler has actually always been able to do inline
| dependencies
|
| https://bundler.io/guides/bundler_in_a_single_file_ruby_scri...
|
| Though I think the implementation could be improved upon
| anotherpaulg wrote:
| Not using shebang, but I've recently been using uv as an
| "installer". It's hard to package and distribute python CLI tools
| with complex dependencies. I used uv in a couple of novel ways:
|
| 1. I copied their `curl | sh` install script and added a `uv tool
| install --python python3.12 my-tool` line at the end. So users
| can now install my CLI tool with a nice curl-pipe-sh one-liner.
|
| 2. I made a tiny pypi "installer" package that has "uv" as its
| only dependency. All it does is `uv tool install` my CLI tool.
|
| Both methods install the CLI tool, python3.12 and all python
| dependencies in their own isolated environment. Users don't need
| to manage python virtual envs. They don't need to even have
| python installed for the curl-pipe-sh method.
|
| I now get far fewer GitHub issues from users who have somehow
| mangled the complex dependencies of my tool.
|
| I wrote it up with more detail and links to code:
|
| https://aider.chat/2025/01/15/uv.html
| alin23 wrote:
| I recently learned about swift-sh [1] which is the same thing as
| "uv run --script" but for swift files.
|
| I've been having a blast automating some macOS annoyances with
| small scripts that can use native macOS APIs directly, no more
| pyobjc.
|
| I also wrote an app to bring the startup folder functionality
| from Windows to the Mac to help me run these scripts at startup
| more easily [2]
|
| [1] https://github.com/mxcl/swift-sh
|
| [2] https://github.com/FuzzyIdeas/StartupFolder
___________________________________________________________________
(page generated 2025-01-28 23:00 UTC)