- En un flujo donde colocas scripts de automatización en
~/bin/, usar uv y metadatos de script en línea permite manejar scripts de Python de forma más ligera
- Si
~/bin está en el PATH, puedes definir el shebang como #!/usr/bin/env -S uv run --script y ejecutar el archivo por su nombre como si fuera un comando
- Esta configuración hace que el shell ejecute primero
uv run --script y le pase el archivo del script actual como argumento
- uv ignora la línea shebang y luego ejecuta el resto del contenido como un archivo normal de Python
- Si le das permisos de ejecución con
chmod a+x {filname}, puedes usarlo como un script simple de línea de comandos en Python que resuelve dependencias automáticamente
Ejecutar uv como shebang
- Para automatizar tareas, se pueden crear y usar varios scripts en el directorio
~/bin/
- Si
~/bin está incluido en el PATH, puedes ejecutar los scripts directamente solo con el nombre del archivo
- El shebang que se usa en este caso es el siguiente
#!/usr/bin/env -S uv run --script
Flujo de ejecución y configuración de permisos
- El shebang anterior ejecuta
uv run --script desde la línea de comandos y pasa el archivo del script como argumento
- uv ignora el shebang y luego ejecuta el resto del archivo como código Python normal
- Antes de ejecutarlo, debes darle permisos de ejecución al script
chmod a+x {filname}
- Una vez configurado, puedes ejecutar de inmediato un script simple de línea de comandos en Python que maneja dependencias automáticamente
1 comentarios
Comentarios de Hacker News
Hoy me enteré por primera vez de env -S, y en cuanto vi el shebang del artículo pensé: “eso no va a funcionar en Linux, porque el shebang solo puede pasar un argumento”
Por ejemplo, si ejecutas
foo.pyque empieza con#!/usr/bin/env -S uv run --script, el sistema operativo en realidad pasa toda la línea shebang como un solo argumento, algo como/usr/bin/env '-S uv run --script' foo.pyPero
envcon-Svuelve a dividir esa cadena en varios argumentos, así que sí funciona. Muy genial y útilPones
#!/usr/bin/guile \y en la siguiente línea-e main -s, luego lo cierras con!#, y termina siendo algo como/usr/bin/guile -e main -s filenameMe pregunto por qué se tomaron la molestia de hacerlo así. Supongo que
env -Sse agregó hace relativamente poco, o no estaba disponible en todas las plataformas que les importabanhttps://www.gnu.org/software/guile/manual/html_node/The-Meta-Switch.html
makepero que usa YAML para la configuraciónPuedes poner
#!/usr/bin/env -s go run github.com/rliebz/tusk@latest -fcomo shebang, y usar como intérpreteinterpreter: go run mvdan.cc/sh/v3/cmd/gosh@latest -s, que es el shell de Go llamado goshAsí obtienes un CLI ejecutable en cualquier arquitectura con solo tener Go instalado
Por ejemplo, nix-shell usa
#!/usr/bin/env nix-shelly en la línea siguiente#!nix-shell --pure -i runghc ./default.nixpara indicar dependencias y argumentos, y después puedes seguir con código HaskellLas extrañas reglas de separación por espacios en la línea shebang son una vieja falla que terminó convertida en una imperfección intocable de Unix
Cada vez que veo estos trucos pienso que con solo 30 minutos de trabajo en los años 80 se podrían haber evitado muchos años de frustración. La línea shebang debió dividirse desde el principio como
/bin/shNo es sobre shebangs, pero últimamente he estado usando uv como herramienta de instalación
Como empaquetar y distribuir herramientas CLI de Python con dependencias complejas es difícil, lo he usado de dos maneras
Una es copiar un script de instalación
curl | shy agregar al finaluv tool install --python python3.12 my-tool, para que el usuario instale el CLI con una sola línea; la otra es crear un pequeño paquete “instalador” en PyPI que solo tengauvcomo dependencia y que luego ejecuteuv tool installEn ambos casos se instala la herramienta CLI, Python 3.12 y todas las dependencias de Python en un entorno aislado. El usuario no tiene que gestionar entornos virtuales, y en el enfoque de
curl | shni siquiera necesita tener Python instaladoDesde entonces han bajado muchísimo los issues en GitHub causados por usuarios que rompen dependencias complejas
Más detalles y enlaces al código: https://aider.chat/2025/01/15/uv.html
openssl-dev,gzipyffi, además de compilar PythonAl menos esa fue mi experiencia al usar ASDF-VM, por cómo funcionan por dentro otras herramientas de instalación de Python
miseyaquatambién tienen un concepto útil de instalación de herramientasYa que salió el tema de shebangs curiosos, hice esto para ejecutar archivos Prolog
.pldesde el shell/*usr/bin/env scryer-prolog "$0" "$@" ; exit #*/Probablemente funcione en general con lenguajes de scripting que tengan comentarios
/**/, pero no soporten directamente comentarios#ni shebang#!Comentario original: https://github.com/mthom/scryer-prolog/issues/2170#issuecomment-1821713993
Este archivo es un archivo de shell válido y al mismo tiempo un archivo Prolog válido. Si se ejecuta como shell, la primera línea busca el patrón glob
/*usr/bin/envy ejecuta/usr/bin/env, yenvluego ejecutascryer-prolog "test.pl". Del lado de Prolog, la primera línea se ignora porque es un comentario/* ... */Después, el shell ejecuta
exitdespués de;para no interpretar el resto del código Prolog como shell, y#hace que el shell ignore*/, que es el cierre de comentario de Prolog, evitando así la salida de erroresPuede que sea lo mejor y lo peor que he hecho
Además, los programas en C también se pueden hacer ejecutables usando una primera línea con la forma
//usr/bin/env sh -c .... Al ejecutarlo, se compila conmakey luego se corre el binario resultante, así que si modificas el código fuente y lo vuelves a ejecutar, ves de inmediato la salida actualizadaDespués de
#!/bin/sh, puedes poner en la siguiente línea# the next line restarts using tcl \y hacer que ejecuteexec tclsh "$0" "$@"Aprovecha que en TCL hasta las líneas de comentario permiten continuación de línea
El artículo de Trey Hunner, Lazy self-installing Python scripts with uv, tiene más detalles y ejemplos
https://treyhunner.com/2024/12/lazy-self-installing-python-scripts-with-uv/
printf %.1s 0{1..80} $'\n'ofor i in {1..80}; do echo -n 0; done; echoTampoco me queda muy claro en qué se diferencia el ejemplo de ffmpeg de
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.mp4Sinceramente, la versión en Python se ve más compleja
Para tareas pequeñas, este truco también se puede usar junto con mise (mise-en-place): https://mise.jdx.dev/tasks/toml-tasks.html#shell-shebang
La idea es poner
uv = 'latest'entools, y dentro del bloquerunde la tarea declarar#!/usr/bin/env -S uv run --scriptydependencies = ["requests<3", "rich"], y luego escribir el código PythonHace 16 días también hubo un artículo relacionado y discusión: Uv's killer feature is making ad-hoc environments easy https://news.ycombinator.com/item?id=42676432 (502 puntos, 417 comentarios)
Es un truco que vale la pena recordar
Hace poco cambié mi alias de Python a
uv run pythony resulta bastante cómodo. No hace falta gestionar.venvni preocuparse por instalaciones del sistema o instalaciones globales/de usuario, y en Debian ayuda especialmenteIncluso al ejecutar un REPL dentro de un proyecto o entorno, se puede invocar sin
activate, así que hay menos cosas en qué pensarEso sí, cuando llamas un
.pydirectamente con uv, las rutas de archivos del proyecto/entorno se resuelven según el directorio de trabajo actual, no según la ubicación del archivo.py, y eso es una desventajaExiste la opción explícita
--project, así que se puede usar comouv run --project /script.py, pero estaría bueno que hubiera una opción de proyecto relativa al archivo objetivo para reducir repeticionesHe usado
uvouvxcomo ejecutor de comandos y me he topado con algunas trampas, pero la mayoría de las veces funciona bien, y cuando funciona ahorra muchísimo tiempoComo desarrollador de Python y persona curiosa, tengo el directorio home lleno de clones superficiales y directorios de pruebas de un solo uso, así que es realmente útil que reduzca esa carga de mantenimiento
Quiero robarme esta idea. Me pregunto si usan una extensión aparte en vez de
.py, o si el hecho de que un archivo.pytenga el bit de ejecución activado y un shebang sirve como señal para ejecutarlo directamente como script en vez de con el Python del sistemaNo son cero, pero las he estado buscando bastante en serio. Si puedes contar qué trampas encontraste, me gustaría agregarlas a la lista
.pycon el bit de ejecución y la línea shebang que aparece en el artículoCon Pipenv tuve problemas cuando cierta dependencia tenía ruedas distintas según la plataforma, mientras que con Poetry funcionó bien
El archivo de bloqueo de Pipenv solo incluía las dependencias de la plataforma donde se bloqueó, pero Poetry agregaba al lockfile todas las variantes de plataforma
Todavía no he encontrado documentación sobre cómo se comporta uv en ese caso
La idea central es esta
Un PEP publicado recientemente define una forma de que los scripts de Python declaren dependencias en comentarios del encabezado, y uv es un ejecutor de scripts de Python y administrador de paquetes que escanea esas dependencias, las satisface y luego ejecuta el script con esos módulos disponibles para importar
Si en el shebang, que es el comentario de la primera línea del script de Python, haces que el archivo invoque
uven lugar depython, obtienes ese efecto automáticamente al “ejecutar” el scriptEn consecuencia, hasta los scripts de Python con dependencias que requieren configuración se pueden ejecutar sin más
$PATHY aunque uv instalará las dependencias, no está claro si con este método también instala automáticamente el intérprete de Python
Personalmente, no me parece que eso ahorre tiempo frente a la forma antigua de crear el entorno e invocarlo
Tampoco tengo claro si uv puede cargar el entorno solo en sesiones interactivas o si se usa únicamente para ejecutar scripts
Si te gusta TypeScript, también puedes usar este mismo enfoque con Deno. Supongo que con Bun también se puede, pero no lo he probado
Se escribe como
#! /usr/bin/env -S deno run, y si necesitas flags de permisos puedes agregarlas como#! /usr/bin/env -S deno run --allow-env --allow-read --allow-net