23 puntos por GN⁺ 2025-12-31 | 1 comentarios | Compartir por WhatsApp
  • Presentación de un truco para ejecutar archivos Go directamente como si fueran ejecutables
  • Si pones //usr/local/go/bin/go run "$0" "$@"; exit en la primera línea y le das permisos de ejecución, puedes ejecutarlo como ./script.go
  • Este método no usa shebang, sino que aprovecha el comportamiento de POSIX de hacer fallback a /bin/sh cuando ocurre ENOEXEC
  • El shell ejecuta la primera línea como comando, y el compilador de Go la reconoce como comentario // y la ignora
  • Con "$0" se pasa la ruta del propio archivo para que go run construya y ejecute el script, y con $@ se pasan los argumentos
  • La sólida biblioteca estándar y la garantía de compatibilidad hacia atrás de Go lo hacen apropiado para scripting, y mientras se use una versión Go 1.x, el script puede seguir funcionando durante décadas
  • Se puede evitar la complejidad de gestionar dependencias como entornos virtuales de Python, pip/poetry/uv, etc.

Cómo funciona el falso shebang

  • El shebang (#!) especifica un intérprete mediante la llamada al sistema execve, pero la técnica presentada en este artículo no es un shebang
  • Consiste en poner //usr/local/go/bin/go run "$0" "$@"; exit en la primera línea del archivo fuente Go y dejar código Go normal debajo de package main
    • Si le das permisos de ejecución con chmod +x script.go, puede ejecutarse como ./script.go
  • Si lo verificas con strace, cuando el shell intenta ejecutar ./script.go con execve, el kernel devuelve ENOEXEC (Exec format error)
    • Al recibir ENOEXEC, el shell usa /bin/sh como fallback e interpreta el archivo como un script de shell
    • En el shell, // no es un comentario, sino que se interpreta como la ruta raíz (/), así que //usr/local/go/bin/go se ejecuta como una ruta válida
  • Por eso la primera línea //usr/local/go/bin/go run "$0" "$@"; exit se ejecuta como comando en el shell
    • Como "$0" pasa la ruta del archivo ejecutado, en la ejecución "$0" se convierte en la ruta de script.go, y go run encuentra, construye y ejecuta el propio archivo
    • "$@" expande los argumentos posicionales desde el primero, permitiendo llamadas como ./script.go -f flag0 here are some args
    • Si no estuviera ; exit, sh seguiría interpretando el archivo Go línea por línea y daría error al encontrar tokens como package

Por qué Go es adecuado para scripting

  • La garantía de compatibilidad hacia atrás de Go es una característica clave, así que mientras uses Go 1.x, los scripts escritos seguirán funcionando a largo plazo
  • La biblioteca estándar madura y las herramientas integradas (formateador, linter, etc.) se ofrecen sin configuración adicional, lo que maximiza el intercambio y la portabilidad de scripts
    • A diferencia de Python, se puede ejecutar código sin tener que aprender sobre entornos virtuales ni distintos gestores de paquetes como pip, poetry o uv
    • Con las herramientas integradas del ecosistema Go y la integración con IDE, se pueden usar formateadores y linters por defecto incluso sin .pyproject ni package.json
  • Si tienes instalada una versión moderna de Go, puede ejecutarse en cualquier sistema operativo durante décadas

Comparación con otros lenguajes compilados

  • Rust tiene compilación lenta y una biblioteca estándar limitada, por lo que es indispensable usar dependencias, y su exigencia de perfección hace más lento el desarrollo
  • Java y los lenguajes de la JVM ya cuentan con lenguajes de scripting basados en bytecode de la JVM, y el scripting ligero con Kotlin también puede ser una alternativa
  • Entre los lenguajes compilados, Go tiene las características más adecuadas para usarse como lenguaje de scripting

Problema de formateo con gopls y solución

  • gopls exige un espacio después del comentario (//example// example), así que rompe la línea del falso shebang
  • Si se inserta el espacio, queda // usr/local/go/bin/go, y el shell ya no lo reconoce como ruta
  • Solución: usar un comentario de bloque /**/ en lugar de //, como se propone en el hilo de HN
    • Se escribe con la forma /*usr/local/go/bin/go run "$0" "$@"; exit; */
    • El punto y coma (;) después de exit es obligatorio

1 comentarios

 
GN⁺ 2025-12-31
Opiniones en Hacker News
  • La parte donde el autor dice “no me importa pip vs poetry vs uv” en realidad uv sí cubre directamente este caso de uso
    Incluso incluyendo dependencias de PyPI, basta con tener instalada la versión de Python y uv
    Enlace a la documentación oficial de uv

    • Incluso hay una forma mejor
      #!/usr/bin/env -S uv run --python 3.14 --script
      Así, aunque Python en sí no esté instalado, uv descarga la versión indicada y la ejecuta
    • Yo también pensaba eso, pero para quienes no usan Python todavía no es algo intuitivo
      Cuando alguien empieza con Clojure, casi siempre le recomiendan usar Leiningen, pero si buscas sobre Python te salen venv, poetry, hatch, uv y varias cosas más
      uv se está volviendo cada vez más dominante, pero todavía no es algo universal
      Una vez instalé Go con apt y la versión era tan vieja que tuve que reinstalarlo, pero eso se resolvió mucho más rápido
      El problema de los entornos virtuales en Python sigue siendo complejo
    • Yo resolví este problema en 2019 con PyFlow
      Era una herramienta OSS escrita en Rust que gestionaba automáticamente la versión de Python y el venv
      Solo configurabas pyproject.toml y ejecutabas pyflow main.py; instalaba y fijaba dependencias como Cargo, y además ajustaba automáticamente la versión de Python adecuada para el proyecto
      En ese momento Poetry y Pipenv eran populares, pero se quedaban cortos en venv y gestión de versiones
    • Yo también me pasé casi por completo a uv
      Uso sobre todo uv add, y solo recurro a uv pip cuando hace falta
      Pero uv pip arrastra las mismas limitaciones de pip — la resolución de dependencias cambia según el orden de instalación
      No da lo mismo instalar dep-a con uv pip install dep-a y luego dep-b, que invertir el orden o instalar ambas juntas
      Esto se parece más a un problema de pip, pero el caos de la gestión de paquetes en Python sigue ahí
    • En realidad ni siquiera hace falta especificar la versión de Python
      uv la descarga por su cuenta
  • Go rechazó explícitamente dar soporte a shebang
    En su lugar, se recomienda usar gorun
    Se puede ejecutar con un truco POSIX como /// 2>/dev/null ; gorun "$0" "$@" ; exit $?
    Nim, Zig y D se pueden usar de forma parecida con la opción -run, y Swift, OCaml y Haskell permiten ejecutar el archivo directamente
    Enlace a la discusión relacionada

    • Para scripts pequeños, el intérprete yaegi podría ser mejor que go run
      yaegi en GitHub
  • El texto de “no quiero saber la diferencia entre pip, poetry y uv, solo quiero ejecutar el código” al final es un tema de dominio técnico
    uv run y PEP 723 ya resolvieron todo el problema

    • Sí, pero uv run tardó demasiado en aparecer
      Llevo más de 20 años usando Python, pero cualquier codebase con paquetes externos o venv siempre me dio miedo
      Gracias a uv run, todos los proyectos de la empresa ya los migré, pero mis proyectos personales ya se fueron a Go
      A largo plazo prefiero un lenguaje con tipos estáticos
    • En un lenguaje viejo, al final siempre terminas aprendiendo bibliotecas competidoras
    • Esto es un problema de UX
      El usuario solo quiere que el programa corra
      uv run y PEP 723 resolvieron el problema, pero sigue habiendo una barrera de entrada porque primero tienes que conocer uv
      Mientras uv no sea la herramienta oficial por defecto, mucha gente va a abandonar Python
  • Me parece una idea realmente genial
    Pero el scripting requiere una ergonomía distinta a la del software de distribución
    bash es improvisado, Go sirve bien para productizar, Python está más o menos en medio, Ruby está más cerca de bash, y Rust más del lado de Go
    Los scripts son útiles para combinar rápidamente comandos del sistema y resolver tareas puntuales
    A Go le falta esa espontaneidad

    • También coincido con eso de que Python está “más o menos en medio”
      En Debian traté de ejecutar una app sencilla de gtk con uv; aunque todas las dependencias estaban correctas, no arrancó y al final terminó en Core Dump
      Cada vez que intento darle otra oportunidad a Python me pasa algo así
      Go es verboso, pero una vez compilado simplemente funciona
    • Yo siento algo parecido
      La clave es si puedes resolverlo todo en un solo archivo
      En Go sí puedes hacer un script de 500 líneas, pero el lenguaje en sí asume varios archivos y módulos
      La falta de bang-line también va por ahí
      Si de todos modos go run crea un binario temporal, entonces me parece mejor compilarlo y ponerlo en /usr/local/bin
    • Decir que bash está más cerca de los comandos del SO es una idea equivocada
      bash también es una capa de abstracción sobre el SO, tanto como Python; solo se siente así porque es el shell por defecto
    • En una época donde los LLM modifican código por ti, quizá la legibilidad termine siendo más importante que la ergonomía al escribirlo
      Especialmente en la dirección de hacer más fácil de leer para humanos el código escrito por LLM
  • Estoy de acuerdo en que alguien que empieza con Python no tiene por qué saber la diferencia entre pip, poetry y uv
    Pero si un bloguero va a escribir sobre este tema, como mínimo debería saber que uv resuelve el problema
    Una crítica ignorante no resulta convincente

    • Hay quien pregunta si uv resuelve lo de “write once, run anywhere” al estilo Go
      Yo tampoco entiendo del todo el concepto de uv, así que también me da curiosidad
  • A mí me gusta hacer scripts en Python
    Permite trabajar rápido y va bien para resolver cosas simples sin preocuparte por tipos o memoria
    Pero no lo usaría para aplicaciones grandes

    • A mí también me gusta el scripting en Python, pero no me gusta instalar scripts de otras personas
    • Este es un enfoque muy centrado en Linux
      La mayoría de los sistemas ya trae Python por defecto, así que para scripts sencillos alcanza de sobra
      Si piensas en que tendrías que instalar Go, me parece mejor usar Python con uv
      Como el mismo autor dijo que “empezó medio por troleo”, al final esto es más bien una cuestión de preferencia por Go
    • También creo que JS no está nada mal como lenguaje de scripting
      Con node bla.js ya está
    • Siempre hay que preocuparse por los tipos
      Tienes que saber qué devuelve una función, y si conoces bien el lenguaje, los tipos básicos ya los resuelves de memoria
      Eso también pasa en los lenguajes con tipos estáticos
    • Python es excelente para el desarrollador, pero para distribución o integración es una pesadilla
      Si estás pensando en otras personas, no deberías escribir código para distribución en Python
  • Esperaba una crítica a Python, pero al final fue más bien un tip útil
    Si el lenguaje usa // como comentario, se puede adaptar este truco
    Sirve para C/C++, Java, JavaScript, Rust, Swift, Kotlin, ObjC, D, F#, GLSL, etc.
    En especial me parece interesante hacer demos gráficas de un solo archivo en GLSL
    Ejemplo en Shadertoy
    En C también se puede usando comentarios de bloque, con algo como /*/../usr/bin/env gcc "$0" "$@"; ./a.out; rm -vf a.out; exit; */

    • Swift tiene el proyecto swift-sh, que permite ejecutar scripts con dependencias externas
      Es una idea parecida a uv, pero para Swift
      Swift también soporta shebang oficialmente
    • En C/C++ se puede escribir #! directamente
      En la época de TCC usé este enfoque para “scripting en C”
      En proyectos grandes, el script de build leía el manifiesto, compilaba y luego ejecutaba
      Pero como es difícil controlar el entorno, no sirve mucho para trabajo real
    • Rust no necesita este tipo de trucos
      Soporta shebang directamente
  • Si quieres un lenguaje más ergonómico, .NET 10 también trae la función “run file directly”
    Soporta shebang e instala paquetes automáticamente dentro del script
    Con la directiva #:sdk incluso puedes ejecutar una webapp al instante

    • Hoy mismo escribí por primera vez un script en C# con esta función y la experiencia fue bastante buena
      Aunque la compilación AOT todavía se siente algo verde
  • Al principio pensé que sería una crítica a Python, pero más bien me hizo pensar en la dirección de los ecosistemas de lenguajes
    Me parece un gran error que ML haya quedado atado a Python
    Porque es lento, su sistema de tipos es incómodo y la distribución es difícil
    Ahora ya deberíamos considerar alternativas como TypeScript, Go o Rust

    • De acuerdo
      Aunque la razón por la que ML eligió Python fue su FFI basado en C
      NodeJS, Rust y Go son débiles en FFI
      Python tiene una ventaja fuerte ahí
      Lo ideal sería un lenguaje tan simple como Python, pero con mejor sistema de tipos y mejor despliegue
    • No estoy de acuerdo con reemplazarlo por TypeScript
      No quiero sustituir Python por un lenguaje salido del ecosistema JS
    • ML terminó en Python por presión del mercado
      Lisp o Lua (Torch) eran más adecuados, pero se eligió Python por su simplicidad
      Yo también estoy desarrollando un framework de ML basado en Lisp, pero dudo que logre adopción
    • El infierno de dependencias de Python sigue siendo grave
      Por problemas de compatibilidad de versiones, ausencia de semver y un ecosistema inestable, da la impresión de estar por detrás de JS
      JS/Node maduró mucho en los últimos 10 años, pero Python sigue detenido en 2012
      De verdad da pena que ML se haya estandarizado sobre Python
    • Yo quiero un lenguaje simple y expresivo, pero con tipado fuerte + compilación nativa
      Al hacer herramientas CLI, Go es muchísimo más rápido que Python
      Volví a Python por la diferencia en LOC, pero cada vez que lo ejecuto extraño Go
      Probablemente OCaml sería ideal, pero su tooling anticuado pesa demasiado
  • El problema de los scripts en Go es que la primera línea no puede llevar espacios
    Porque gopls obliga al formateo automático
    Y como en CI también hay que mantener consistencia de formato, esto importa en la práctica
    Pero el problema mayor es que no se puede usar go.mod
    O sea, no puedes fijar versiones de dependencias, así que la garantía de compatibilidad queda más débil

    • Aun así, las versiones mayores quedan fijadas por la ruta de importación, así que en lo básico sí hay compatibilidad
    • Eso es un problema de compatibilidad a nivel de lenguaje/runtime, no un problema de dependencias