- Dans un flux où l’on place des scripts d’automatisation dans
~/bin/ pour les exécuter, l’utilisation de uv et des métadonnées de script inline permet de gérer plus simplement les scripts Python
- Si
~/bin est présent dans le PATH, on peut définir le shebang sur #!/usr/bin/env -S uv run --script afin d’exécuter le fichier par son nom comme une commande
- Cette configuration fait en sorte que le shell exécute d’abord
uv run --script, puis lui transmette le fichier du script courant comme argument
- uv ignore ensuite la ligne shebang et exécute le reste du contenu comme un fichier Python classique
- En donnant les droits d’exécution avec
chmod a+x {filname}, on peut l’utiliser comme un simple script Python en ligne de commande avec gestion automatique des dépendances
Exécuter uv comme shebang
- Pour automatiser des tâches, on crée et utilise plusieurs scripts dans le répertoire
~/bin/
- Si
~/bin est inclus dans le PATH, les scripts peuvent être exécutés directement uniquement avec leur nom de fichier
- Le shebang à utiliser dans ce cas est le suivant
#!/usr/bin/env -S uv run --script
Flux d’exécution et configuration des permissions
- Le shebang ci-dessus lance
uv run --script depuis la ligne de commande, puis transmet le fichier du script comme argument
- uv ignore le shebang et exécute le reste du fichier comme du code Python ordinaire
- Avant l’exécution, il faut accorder au script les droits d’exécution
chmod a+x {filname}
- Une fois la configuration terminée, il est possible d’exécuter directement un simple script Python en ligne de commande qui gère automatiquement ses dépendances
1 commentaires
Commentaires sur Hacker News
J’ai découvert env -S aujourd’hui et, dès que j’ai vu le shebang dans l’article, je me suis dit : « ça ne marchera pas sous Linux, puisqu’un shebang ne peut transmettre qu’un seul argument »
Par exemple, si l’on exécute un
foo.pyqui commence par#!/usr/bin/env -S uv run --script, le système d’exploitation transmet en réalité toute la ligne de shebang comme un seul argument, comme dans/usr/bin/env '-S uv run --script' foo.pyMais l’option
-Sdeenvredécoupe cette chaîne en plusieurs arguments, ce qui fait que ça fonctionne. C’est très élégant et utileEn mettant
#!/usr/bin/guile \puis-e main -ssur la ligne suivante, et en fermant avec!#, on obtient quelque chose comme/usr/bin/guile -e main -s filenameJe me demande pourquoi ils ont choisi de faire comme ça. Peut-être que
env -Sest un ajout récent, ou qu’il n’était pas disponible sur toutes les plateformes qui les intéressaienthttps://www.gnu.org/software/guile/manual/html_node/The-Meta-Switch.html
makemais qui utilise YAML pour la configurationOn met
#!/usr/bin/env -s go run github.com/rliebz/tusk@latest -fcomme shebang, et l’interpréteur utilise gosh, un shell en Go, avec quelque chose commeinterpreter: go run mvdan.cc/sh/v3/cmd/gosh@latest -sOn obtient ainsi une CLI exécutable sur n’importe quelle architecture tant que Go est installé
Par exemple, nix-shell permet de mettre
#!/usr/bin/env nix-shell, puis#!nix-shell --pure -i runghc ./default.nixsur la ligne suivante pour préciser les dépendances et les arguments, avant de continuer avec le code HaskellLes étranges règles de découpage par espaces des lignes de shebang sont un vieux bug devenu une tare Unix impossible à corriger
Chaque fois qu’on utilise ce genre de bidouille, je me dis qu’en y consacrant 30 minutes dans les années 1980, on aurait évité des années d’agacement. Les lignes de shebang auraient dû être découpées comme
/bin/shdès le débutCe n’est pas un shebang, mais récemment j’utilise uv comme outil d’installation
Comme il est difficile de packager et distribuer des outils CLI Python avec des dépendances complexes, je l’ai utilisé de deux façons
La première consiste à reprendre un script d’installation
curl | shet à ajouter à la finuv tool install --python python3.12 my-tool, afin que l’utilisateur puisse installer la CLI en une seule ligne ; la seconde consiste à créer un petit paquet PyPI « installateur » qui n’a queuvcomme dépendance et qui exécuteuv tool installDans les deux cas, l’outil CLI, Python 3.12 et toutes les dépendances Python sont installés dans un environnement isolé. L’utilisateur n’a pas à gérer d’environnement virtuel et, avec l’approche
curl | sh, il n’a même pas besoin d’avoir Python installéDepuis, le nombre d’issues GitHub causées par des utilisateurs qui cassent des dépendances complexes a nettement diminué
Plus de détails et liens vers le code : https://aider.chat/2025/01/15/uv.html
openssl-dev,gzip, des en-têtes de bibliothèques commeffi, et compiler PythonC’est en tout cas l’expérience que j’ai eue avec ASDF-VM, à cause d’autres outils d’installation de Python utilisés en interne
miseetaquasemblent aussi avoir une notion utile d’installation d’outilsPuisqu’on en est aux histoires de shebang amusantes, j’ai bricolé ceci pour exécuter des fichiers Prolog
.pldepuis le shell/*usr/bin/env scryer-prolog "$0" "$@" ; exit #*/Ça devrait globalement marcher avec les langages de script qui ont les commentaires
/**/, mais ne prennent pas directement en charge les commentaires#ni les shebangs#!Commentaire d’origine : https://github.com/mthom/scryer-prolog/issues/2170#issuecomment-1821713993
Ce fichier est à la fois un fichier shell valide et un fichier Prolog valide. Exécuté par le shell, la première ligne cherche le motif glob
/*usr/bin/envet exécute/usr/bin/env, puisenvlancescryer-prolog "test.pl". Côté Prolog, la première ligne est ignorée car c’est un commentaire/* ... */Ensuite, le shell exécute
exitaprès le;pour éviter d’interpréter le reste du code Prolog comme du shell, et#fait en sorte que le shell ignore le marqueur de fin de commentaire Prolog*/, ce qui évite les messages d’erreurC’est peut-être à la fois la meilleure et la pire chose que j’aie faite
En plus de cela, on peut aussi rendre un programme C exécutable avec une première ligne de la forme
//usr/bin/env sh -c .... À l’exécution, il est compilé avecmake, puis le binaire résultant est lancé ; si l’on modifie le source puis qu’on le relance, la sortie modifiée apparaît immédiatementOn peut mettre
# the next line restarts using tcl \sur la ligne suivant#!/bin/sh, puis faire exécuterexec tclsh "$0" "$@"Cela exploite le fait qu’en TCL, même les lignes de commentaire peuvent utiliser la continuation de ligne
L’article de Trey Hunner, Lazy self-installing Python scripts with uv, donne plus de détails avec des exemples
https://treyhunner.com/2024/12/lazy-self-installing-python-scripts-with-uv/
printf %.1s 0{1..80} $'\n'oufor i in {1..80}; do echo -n 0; done; echoPour l’exemple ffmpeg, je ne vois pas bien en quoi c’est différent 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.mp4Honnêtement, la version Python me paraît plus compliquée
Pour les petites tâches, on peut utiliser cette astuce avec mise (mise-en-place) : https://mise.jdx.dev/tasks/toml-tasks.html#shell-shebang
L’idée est de mettre
uv = 'latest'danstools, puis, dans le blocrunde la tâche, de déclarer#!/usr/bin/env -S uv run --scriptetdependencies = ["requests<3", "rich"], avant d’y placer le code PythonIl y avait déjà eu un article et une discussion sur le sujet il y a 16 jours : Uv's killer feature is making ad-hoc environments easy https://news.ycombinator.com/item?id=42676432 (502 points, 417 commentaires)
C’est une astuce à garder en tête
J’ai récemment changé mon alias Python en
uv run python, et c’est plutôt agréable. Plus besoin de gérer.venvet autres, ni de se soucier des problèmes d’installation système ou globale/utilisateur ; sur Debian, c’est particulièrement utileMême quand on lance un REPL dans un projet ou un environnement, on peut l’appeler sans
activate, ce qui réduit la charge mentaleEn revanche, quand on appelle directement un
.pyavec uv, l’emplacement du fichier de projet/environnement est déterminé à partir du répertoire de travail courant, et non de l’emplacement du fichier.py, ce qui est un inconvénientIl existe bien l’option explicite
--project, ce qui permet d’écrireuv run --project /script.py, mais ce serait pratique d’avoir une option de projet relative au fichier cible pour éviter les répétitionsEn utilisant
uvouuvxcomme lanceur de commandes, j’ai bien rencontré quelques pièges, mais la plupart du temps ça fonctionne bien, et dans ces cas-là ça fait gagner énormément de tempsEn tant que développeur Python et personne curieuse, mon répertoire personnel est rempli de clones superficiels et de répertoires de tests jetables ; le fait que cela réduise cette charge de gestion est vraiment utile
J’ai envie de reprendre cette idée. Je me demande si l’auteur utilise une extension distincte de
.py, ou bien si le simple fait qu’un fichier.pyait le bit d’exécution activé et un shebang sert de signal indiquant qu’il peut être exécuté directement comme script, plutôt que par le Python du systèmeElle n’est pas vide, mais je cherche assez activement. Si je pouvais savoir quels pièges ont été rencontrés, j’aimerais les ajouter à la liste
.pyavec le bit d’exécution et la ligne de shebang présentée dans l’articleAvec Pipenv, il y avait des problèmes lorsqu’une dépendance donnée avait des wheels différentes selon la plateforme, alors qu’avec Poetry cela fonctionnait bien
Le fichier de verrouillage de Pipenv n’incluait que les dépendances de la plateforme sur laquelle il avait été verrouillé, tandis que Poetry ajoutait toutes les variantes de plateformes au fichier de verrouillage
Je n’ai pas encore trouvé de documentation indiquant comment uv se comporte dans ce cas
L’essentiel est le suivant
Un PEP récemment publié définit la manière dont les scripts Python peuvent déclarer leurs dépendances dans les commentaires initiaux, et uv est à la fois un lanceur de scripts Python et un gestionnaire de paquets qui analyse ces dépendances, les satisfait, puis exécute le script dans un état où ces modules peuvent être importés
Si, dans le shebang qui est le commentaire de première ligne d’un script Python, on fait appeler le fichier par
uvau lieu depython, on obtient automatiquement cet effet lorsque l’on « exécute » le scriptRésultat : même les scripts Python ayant des dépendances nécessitant une configuration s’exécutent simplement
$PATHEt même si uv installera les dépendances, il n’est pas clair s’il installe aussi automatiquement l’interpréteur Python avec cette méthode
Personnellement, je ne vois pas en quoi cela fait gagner du temps par rapport à l’ancienne méthode consistant à créer un environnement puis à l’appeler
Je ne sais pas non plus très bien si uv peut charger un environnement dans une session interactive, ou s’il sert uniquement à l’exécution de scripts
Si vous aimez TypeScript, vous pouvez utiliser la même approche avec Deno. Bun devrait probablement aussi marcher, mais je n’ai pas essayé moi-même
On écrit quelque chose comme
#! /usr/bin/env -S deno run, et si des flags d’autorisations sont nécessaires, on peut les ajouter ainsi :#! /usr/bin/env -S deno run --allow-env --allow-read --allow-net