Unleash the power of uv shebangs

A shebang is the character sequence #! used at the start of a text file in POSIX systems to specify which piece of software should be used to execute the contents of the file, for example:

#! python
print("Nobody expects the Spanish Inquisition!")

In theory, #! python is sufficient. In practice, to ensure that your operating system can find the python executable, one often uses #! /usr/bin/env python instead. The executable env is used to locate python, wherever it might reside on your computer.

But this means that to use the above script, you need to have Python installed. Python is notorious for its dependency hell, meaning that it is very easy to find yourself in a situation where several independent Python distributions coexit within the same operating system (so that your shebang might be calling the wrong Python version), or where updating the Python library needed by one script (“dependency”) will break another script.

All this messiness can be avoided by using uv, a Python dependency manager with the following selling points:

To use uv, you first have to install it, then modify your shebang accordingly. There is absolutely no need to install Python for this to work.

#! /usr/bin/env uv --no-project run python
print("Nobody expects the Spanish Inquisition!")

In the above code, --no-project means that if you were to invoke the script in a directory where another set of requirements normally applies (e.g., as a result of a pyproject.toml file), these additional requirements would be ignored while running your script.

The true power of uv shebangs lies in the ability to specify dependencies within the shebang line. For example, if your script needs numpy:

#! /usr/bin/env uv run --no-project --with numpy python
from numpy import eye
print(eye(5))

One may require a specific version of numpy:

#! /usr/bin/env uv run --no-project --with numpy==2.1.2 python
import numpy
print(numpy.__version__)

One may also require more than one dependency:

#! /usr/bin/env uv run --no-project --with matplotlib==3.9.2 --with scipy==1.14.1 python

Finally, one may require a specific version of Python:

#! /usr/bin/env uv run --no-project --with matplotlib --with scipy --python 3.11.6 python

As a fully functional example, here is a version of the doi2bibtex script described here which only requires uv to work properly:

#! /usr/bin/env uv run --no-project --with requests==2.32.3 --with pyperclip==1.9.0 python

import sys, requests, pyperclip

doi = sys.argv[1]              # DOI is the first command-line argument
url = f'http://doi.org/{doi}'  # construct the URL

# HTTP header to get BibTeX format back
headers = {'Accept': 'text/bibliography; style=bibtex'}

# send out the HTTP request
r = requests.get(url, headers = headers)

r.encoding = 'utf-8'  # specify that the response is UTF8-encoded
bib = r.text[1:-1]    # shave of the first and last characters of the response
pyperclip.copy(bib)   # copy the result to the clipboard
print(bib)            # and print it out as an indicator of success