2 points par GN⁺ 2025-01-29 | 1 commentaires | Partager sur WhatsApp
  • 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

 
GN⁺ 2025-01-29
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.py qui 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.py
    Mais l’option -S de env redécoupe cette chaîne en plusieurs arguments, ce qui fait que ça fonctionne. C’est très élégant et utile

    • Sous macOS, ce comportement n’est pas identique, ce qui est frustrant : https://unix.stackexchange.com/a/774145
    • Cela me rappelle la façon dont GNU Guile contourne la limite d’un seul argument avec un shebang sur plusieurs lignes
      En mettant #!/usr/bin/guile \ puis -e main -s sur la ligne suivante, et en fermant avec !#, on obtient quelque chose comme /usr/bin/guile -e main -s filename
      Je me demande pourquoi ils ont choisi de faire comme ça. Peut-être que env -S est un ajout récent, ou qu’il n’était pas disponible sur toutes les plateformes qui les intéressaient
      https://www.gnu.org/software/guile/manual/html_node/The-Meta-Switch.html
    • Cette approche se prête bien à tusk, une CLI Go proche de make mais qui utilise YAML pour la configuration
      On met #!/usr/bin/env -s go run github.com/rliebz/tusk@latest -f comme shebang, et l’interpréteur utilise gosh, un shell en Go, avec quelque chose comme interpreter: go run mvdan.cc/sh/v3/cmd/gosh@latest -s
      On obtient ainsi une CLI exécutable sur n’importe quelle architecture tant que Go est installé
    • Si le wrapper coopère, on peut aussi placer davantage d’informations sur les lignes qui suivent
      Par exemple, nix-shell permet de mettre #!/usr/bin/env nix-shell, puis #!nix-shell --pure -i runghc ./default.nix sur la ligne suivante pour préciser les dépendances et les arguments, avant de continuer avec le code Haskell
    • env -S n’aurait jamais dû être nécessaire au départ
      Les é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/sh dès le début
  • Ce 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 | sh et à ajouter à la fin uv 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 que uv comme dépendance et qui exécute uv tool install
    Dans 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

    • Je n’ai pas vérifié comment cela fonctionne en interne, mais si cela installe aussi l’outil CLI et Python 3.12, j’imagine qu’il faut sur le système de l’utilisateur final un compilateur, les en-têtes du noyau, openssl-dev, gzip, des en-têtes de bibliothèques comme ffi, et compiler Python
      C’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
    • Bien. mise et aqua semblent aussi avoir une notion utile d’installation d’outils
  • Puisqu’on en est aux histoires de shebang amusantes, j’ai bricolé ceci pour exécuter des fichiers Prolog .pl depuis 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/env et exécute /usr/bin/env, puis env lance scryer-prolog "test.pl". Côté Prolog, la première ligne est ignorée car c’est un commentaire /* ... */
    Ensuite, le shell exécute exit aprè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’erreur
    C’est peut-être à la fois la meilleure et la pire chose que j’aie faite

    • Il existe aussi une méthode similaire pour rendre du code source Go exécutable : https://unix.stackexchange.com/questions/162531/shebang-starting-with
      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é avec make, 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édiatement
    • Je ne l’ai pas inventé moi-même, mais TCL utilise aussi ce genre d’astuce
      On peut mettre # the next line restarts using tcl \ sur la ligne suivant #!/bin/sh, puis faire exécuter exec tclsh "$0" "$@"
      Cela exploite le fait qu’en TCL, même les lignes de commentaire peuvent utiliser la continuation de ligne
    • C’est vraiment impressionnant. Avec quelques ajustements de la redirection d’entrée/sortie de Scryer, ça pourrait devenir un remplaçant de bash script parfait
  • 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/

    • Parmi les exemples, le « script qui affiche 80 zéros » est aussi faisable en shell avec printf %.1s 0{1..80} $'\n' ou for i in {1..80}; do echo -n 0; done; echo
      Pour 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.mp4
      Honnê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' dans tools, puis, dans le bloc run de la tâche, de déclarer #!/usr/bin/env -S uv run --script et dependencies = ["requests<3", "rich"], avant d’y placer le code Python

  • Il 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 .venv et autres, ni de se soucier des problèmes d’installation système ou globale/utilisateur ; sur Debian, c’est particulièrement utile
    Même quand on lance un REPL dans un projet ou un environnement, on peut l’appeler sans activate, ce qui réduit la charge mentale
    En revanche, quand on appelle directement un .py avec 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énient
    Il existe bien l’option explicite --project, ce qui permet d’écrire uv run --project /script.py, mais ce serait pratique d’avoir une option de projet relative au fichier cible pour éviter les répétitions

  • En utilisant uv ou uvx comme 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 temps
    En 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 .py ait 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ème

    • Depuis un an, j’utilise uv moi-même pour voir si je peux le recommander, et je tiens une liste des pièges ; elle s’est révélée étonnamment courte
      Elle 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
    • Si j’ai bien compris, la méthode consiste à utiliser un .py avec le bit d’exécution et la ligne de shebang présentée dans l’article
    • Je suis vraiment curieux de savoir quels pièges il y a eu
      Avec 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 uv au lieu de python, on obtient automatiquement cet effet lorsque l’on « exécute » le script
    Résultat : même les scripts Python ayant des dépendances nécessitant une configuration s’exécutent simplement

    • Il faut toutefois que uv soit installé dans $PATH
      Et 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
    • Au final, ce n’est qu’après avoir installé uv sur le système et ajouté des lignes de dépendances en tête de tous les scripts que ça « s’exécute simplement »
      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