- 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
Opiniones en Hacker News
La parte donde el autor dice “no me importa
pipvspoetryvsuv” en realidad uv sí cubre directamente este caso de usoIncluso incluyendo dependencias de PyPI, basta con tener instalada la versión de Python y uv
Enlace a la documentación oficial de uv
#!/usr/bin/env -S uv run --python 3.14 --scriptAsí, aunque Python en sí no esté instalado, uv descarga la versión indicada y la ejecuta
Cuando alguien empieza con Clojure, casi siempre le recomiendan usar Leiningen, pero si buscas sobre Python te salen
venv,poetry,hatch,uvy varias cosas másuv 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
Era una herramienta OSS escrita en Rust que gestionaba automáticamente la versión de Python y el
venvSolo configurabas
pyproject.tomly ejecutabaspyflow main.py; instalaba y fijaba dependencias como Cargo, y además ajustaba automáticamente la versión de Python adecuada para el proyectoEn ese momento Poetry y Pipenv eran populares, pero se quedaban cortos en
venvy gestión de versionesUso sobre todo
uv add, y solo recurro auv pipcuando hace faltaPero
uv piparrastra las mismas limitaciones de pip — la resolución de dependencias cambia según el orden de instalaciónNo da lo mismo instalar
dep-aconuv pip install dep-ay luegodep-b, que invertir el orden o instalar ambas juntasEsto se parece más a un problema de pip, pero el caos de la gestión de paquetes en Python sigue ahí
uv la descarga por su cuenta
Go rechazó explícitamente dar soporte a shebang
En su lugar, se recomienda usar
gorunSe 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 directamenteEnlace a la discusión relacionada
go runyaegi en GitHub
El texto de “no quiero saber la diferencia entre
pip,poetryyuv, solo quiero ejecutar el código” al final es un tema de dominio técnicouv runy PEP 723 ya resolvieron todo el problemauv runtardó demasiado en aparecerLlevo más de 20 años usando Python, pero cualquier codebase con paquetes externos o
venvsiempre me dio miedoGracias a
uv run, todos los proyectos de la empresa ya los migré, pero mis proyectos personales ya se fueron a GoA largo plazo prefiero un lenguaje con tipos estáticos
El usuario solo quiere que el programa corra
uv runy PEP 723 resolvieron el problema, pero sigue habiendo una barrera de entrada porque primero tienes que conocer uvMientras 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
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
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 runcrea un binario temporal, entonces me parece mejor compilarlo y ponerlo en/usr/local/binbash también es una capa de abstracción sobre el SO, tanto como Python; solo se siente así porque es el shell por defecto
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,poetryyuvPero 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
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
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
Con
node bla.jsya está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
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 trucoSirve 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; */Es una idea parecida a uv, pero para Swift
Swift también soporta shebang oficialmente
#!directamenteEn 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
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
#:sdkincluso puedes ejecutar una webapp al instanteAunque 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
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 quiero sustituir Python por un lenguaje salido del ecosistema JS
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
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
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
goplsobliga al formateo automáticoY 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.modO sea, no puedes fijar versiones de dependencias, así que la garantía de compatibilidad queda más débil