2 pontos por GN⁺ 2025-01-29 | 1 comentários | Compartilhar no WhatsApp
  • Ao usar uv e metadados de script inline no fluxo de manter e executar scripts de automação em ~/bin/, é possível lidar com scripts Python de forma mais leve
  • Se ~/bin estiver no PATH, você pode definir o shebang como #!/usr/bin/env -S uv run --script e executar o arquivo pelo nome, como se fosse um comando
  • Essa configuração faz com que o shell execute primeiro uv run --script e passe o arquivo de script atual como argumento
  • O uv ignora a linha shebang e executa o restante do conteúdo como um arquivo Python comum
  • Ao conceder permissão de execução com chmod a+x {filname}, ele pode ser usado como um script simples de linha de comando em Python que trata dependências automaticamente

Executando uv como shebang

  • Uso vários scripts no diretório ~/bin/ para automatizar tarefas
  • Se ~/bin estiver incluído no PATH, é possível executar scripts diretamente apenas pelo nome do arquivo
  • O shebang a ser usado nesse caso é o seguinte
#!/usr/bin/env -S uv run --script

Fluxo de execução e configuração de permissões

  • O shebang acima executa uv run --script na linha de comando e passa o arquivo de script como argumento
  • O uv ignora o shebang e executa o restante do arquivo como código Python comum
  • Antes de executar, é preciso conceder permissão de execução ao script
chmod a+x {filname}
  • Depois de concluir a configuração, você pode executar diretamente um script simples de linha de comando em Python que trata dependências automaticamente

1 comentários

 
GN⁺ 2025-01-29
Opiniões do Hacker News
  • Hoje conheci env -S pela primeira vez e, assim que vi o shebang no artigo, pensei: “isso não deve funcionar no Linux, já que o shebang só consegue passar um único argumento”
    Por exemplo, ao executar um foo.py que começa com #!/usr/bin/env -S uv run --script, o sistema operacional na prática passa a linha inteira do shebang como um único argumento, como em /usr/bin/env '-S uv run --script' foo.py
    Só que o -S do env divide essa string novamente em vários argumentos, então funciona. Muito bacana e útil

    • No macOS esse comportamento não é igual, o que é frustrante: https://unix.stackexchange.com/a/774145
    • Isso me lembrou a forma como o GNU Guile lida com a limitação de um único argumento usando um shebang de várias linhas
      Colocando #!/usr/bin/guile \ e, na linha seguinte, -e main -s, fechando com !#, fica algo como /usr/bin/guile -e main -s filename
      Fico curioso sobre por que fizeram justamente assim. Talvez o env -S tenha sido adicionado recentemente, ou não estivesse disponível em todas as plataformas que interessavam
      https://www.gnu.org/software/guile/manual/html_node/The-Meta-Switch.html
    • Esse método combina bem com o tusk, uma CLI em Go parecida com make, mas que usa YAML para configuração
      Colocando #!/usr/bin/env -s go run github.com/rliebz/tusk@latest -f como shebang, e usando o gosh, um shell em Go, como interpretador com algo como interpreter: go run mvdan.cc/sh/v3/cmd/gosh@latest -s
      Assim, se Go estiver instalado, vira uma CLI executável em qualquer arquitetura
    • Se o wrapper colaborar, também dá para colocar mais informações nas linhas seguintes
      Por exemplo, o nix-shell permite colocar #!/usr/bin/env nix-shell e, na linha seguinte, #!nix-shell --pure -i runghc ./default.nix, para especificar dependências e argumentos, e depois continuar com o código Haskell
    • env -S nem deveria ter sido necessário para começo de conversa
      As regras estranhas de separação por espaços na linha de shebang são um bug antigo que acabou virando uma falha impossível de corrigir no Unix
      Toda vez que vejo gambiarras como essas, penso que, se tivessem gasto 30 minutos nos anos 1980, teriam evitado anos de irritação depois. A linha de shebang deveria ter sido separada como /bin/sh desde o início
  • Não é shebang, mas ultimamente tenho usado o uv como ferramenta de instalação
    Como é difícil empacotar e distribuir ferramentas CLI em Python com dependências complexas, usei isso de duas formas
    Uma foi copiar um script de instalação curl | sh e adicionar uv tool install --python python3.12 my-tool no final, para permitir que o usuário instale a CLI com uma única linha; a outra foi criar um pequeno pacote “instalador” no PyPI que tem apenas uv como dependência e faz executar uv tool install
    Nos dois casos, a ferramenta CLI, o Python 3.12 e todas as dependências Python são instalados em um ambiente isolado. O usuário não precisa gerenciar ambientes virtuais e, no método curl | sh, nem precisa ter Python instalado
    Desde então, os issues no GitHub causados por usuários quebrando dependências complexas diminuíram bastante
    Mais detalhes e links para o código: https://aider.chat/2025/01/15/uv.html

    • Não verifiquei como funciona internamente, mas, se ele instala a ferramenta CLI e também o Python 3.12, imagino que o sistema do usuário final precise de compilador, headers do kernel e headers de bibliotecas como openssl-dev, gzip e ffi, e que seja necessário compilar o Python
      Pelo menos quando usei ASDF-VM, tive essa experiência por causa de outras ferramentas de instalação de Python usadas por ele
    • Legal. Parece que mise e aqua também têm um conceito útil de instalação de ferramentas
  • Já que surgiu essa história interessante de shebang, fiz algo assim para executar arquivos Prolog .pl no shell
    /*usr/bin/env scryer-prolog "$0" "$@" ; exit #*/
    Se for uma linguagem de script que tenha comentários /**/, mas não ofereça suporte direto a comentários # ou ao shebang #!, em geral acho que deve funcionar
    Comentário original: https://github.com/mthom/scryer-prolog/issues/2170#issuecomment-1821713993
    Esse arquivo é ao mesmo tempo um arquivo de shell válido e um arquivo Prolog válido. Quando executado pelo shell, a primeira linha procura o padrão glob /*usr/bin/env, executa /usr/bin/env, e o env executa scryer-prolog "test.pl". Do lado do Prolog, a primeira linha é ignorada por ser um comentário /* ... */
    Depois disso, o shell executa o exit após o ;, para não interpretar o restante do código Prolog como shell, e o # faz o shell ignorar o terminador de comentário do Prolog, */, evitando mensagens de erro
    Talvez seja a melhor e a pior coisa que já fiz

    • Há também um jeito parecido de tornar código-fonte Go executável: https://unix.stackexchange.com/questions/162531/shebang-starting-with
      Além disso, também dá para tornar um programa em C executável usando uma primeira linha no formato //usr/bin/env sh -c .... Ao executar, ele compila com make e depois executa o binário resultante; então, se você modificar o fonte e executar de novo, a saída alterada aparece imediatamente
    • Não fui eu que inventei, mas em TCL também se usa algo desse tipo
      Dá para colocar #!/bin/sh, depois na linha seguinte # the next line restarts using tcl \, e fazer executar exec tclsh "$0" "$@"
      Isso aproveita o fato de que, em TCL, até linhas de comentário podem ter continuação de linha
    • Isso é realmente incrível. Se ajustarem um pouco o redirecionamento de entrada e saída do Scryer, parece que ficaria perfeito como substituto para scripts bash
  • O artigo de Trey Hunner, Lazy self-installing Python scripts with uv, traz mais detalhes com exemplos
    https://treyhunner.com/2024/12/lazy-self-installing-python-scripts-with-uv/

    • Entre os exemplos, o “script que imprime 80 zeros” também pode ser feito no shell com printf %.1s 0{1..80} $'\n' ou for i in {1..80}; do echo -n 0; done; echo
      Também não entendo muito bem em que o exemplo com ffmpeg é diferente 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
      Sinceramente, a versão em Python parece mais complicada
  • Para tarefas pequenas, dá para usar esse truque junto com mise (mise-en-place): https://mise.jdx.dev/tasks/toml-tasks.html#shell-shebang
    A ideia é colocar uv = 'latest' em tools, declarar #!/usr/bin/env -S uv run --script e dependencies = ["requests<3", "rich"] no bloco run da tarefa, e depois inserir o código Python

  • Há 16 dias também houve um artigo e discussão relacionados: Uv's killer feature is making ad-hoc environments easy https://news.ycombinator.com/item?id=42676432 (502 pontos, 417 comentários)

  • É um truque que vale a pena lembrar
    Recentemente troquei meu alias de Python para uv run python e tem sido bem agradável. Não preciso gerenciar .venv etc., nem me preocupar com instalação no sistema ou instalações globais/de usuário; no Debian, isso ajuda especialmente
    Mesmo ao executar o REPL dentro de um projeto ou ambiente, dá para chamar sem activate, o que reduz o que preciso ter em mente
    Porém, ao chamar um .py diretamente com uv, a desvantagem é que os caminhos dos arquivos de projeto/ambiente são resolvidos com base no diretório de trabalho atual, não com base na localização do arquivo .py
    Existe a opção explícita --project, então dá para usar algo como uv run --project /script.py, mas seria bom ter uma opção de projeto relativa ao arquivo alvo para reduzir a repetição

  • Ao usar uv ou uvx como executores de comandos, já esbarrei em algumas armadilhas, mas na maior parte do tempo funciona bem e, quando funciona, economiza bastante tempo
    Como desenvolvedor Python e pessoa curiosa, meu diretório home vive cheio de clones rasos e diretórios de testes descartáveis; é realmente útil reduzir esse peso de gerenciamento
    Quero roubar essa ideia. Fico curioso se usam uma extensão separada em vez de .py, ou se apenas ter o bit de execução ativado em um arquivo .py e uma shebang serve como sinal de que ele pode ser executado diretamente como script, e não pelo Python do sistema

    • No último ano, venho usando uv por conta própria para ver se vale recomendá-lo e montei uma lista de armadilhas, mas ela acabou sendo surpreendentemente curta
      Não é zero, mas estou procurando com bastante afinco. Se eu puder saber quais armadilhas você encontrou, gostaria de adicioná-las à lista
    • Pelo que entendi, o método é usar .py com o bit de execução e a linha shebang mostrada no artigo
    • Estou realmente curioso sobre quais armadilhas houve
      No Pipenv, havia problemas quando uma dependência específica tinha wheels diferentes por plataforma; no Poetry isso funcionava bem
      O arquivo de lock do Pipenv incluía apenas as dependências da plataforma em que o lock foi feito, enquanto o Poetry adicionava todas as variações de plataforma ao arquivo de lock
      Ainda não encontrei documentação sobre como o uv se comporta nesse caso
  • O ponto principal é este
    Um PEP publicado recentemente define uma forma de scripts Python declararem dependências nos comentários iniciais, e o uv é tanto um executor de scripts Python quanto um gerenciador de pacotes que escaneia essas dependências, as satisfaz e então executa o script em um estado em que esses módulos podem ser importados
    Se, na shebang, o comentário da primeira linha de um script Python, você fizer o arquivo ser chamado com uv em vez de python, obtém automaticamente esse efeito ao “executar” o script
    Como resultado, até scripts Python com dependências que exigem configuração simplesmente rodam

    • Porém, o uv precisa estar instalado no $PATH
      E, embora o uv instale as dependências, não está claro se ele também instala automaticamente o interpretador Python dessa forma
    • No fim, ele só “simplesmente roda” depois de instalar o uv no sistema e colocar linhas de dependência no início de todos os scripts
      Pessoalmente, não me parece economizar tempo em relação ao jeito antigo de criar um ambiente e chamá-lo
      Também não tenho certeza se o uv consegue carregar o ambiente apenas em sessões interativas ou se é usado somente para execução de scripts
  • Se você gosta de TypeScript, dá para usar a mesma abordagem com Deno. Bun também deve funcionar, mas não testei pessoalmente
    Use algo como #! /usr/bin/env -S deno run; se precisar de flags de permissão, adicione-as assim: #! /usr/bin/env -S deno run --allow-env --allow-read --allow-net