Summary of lorentz.app: go-shebang by Lorentz Kinde (2025-12).
Prepend this line to a .go file, chmod +x, then run it directly:
//usr/local/go/bin/go run "$0" "$@"; exit
package main
import "fmt"
func main() {
fmt.Println("Hello world")
}❯ ./script.go
Hello world
Note
Or wherever you have Go install:
e.g. opt/homebrew/bin/go
A real shebang is #!interpreter [arg] and is handled by execve(2). The line above isn't one. What actually happens:
execve("./script.go", ...)fails withENOEXEC— not ELF, no#!.- The shell falls back to running the file as a shell script via
/bin/sh ./script.go. shruns the first line.//is not a shell comment, so//usr/local/go/bin/gois just a path to thegobinary.run "$0" "$@"invokesgo runon the script itself ($0is the script's path) plus any forwarded args.; exitstopsshbefore it tries to interpret the rest of the Go source as shell.- Go's compiler ignores the first line because
//is a Go comment.
Verified via strace:
execve("/usr/bin/sh", ["sh", "-c", "./script.go"], ...) = 0
execve("./script.go", ["./script.go"], ...) = -1 ENOEXEC
execve("/bin/sh", ["/bin/sh", "./script.go"], ...) = 0
- Go has a strong standard library and Go 1.x compatibility guarantees — scripts keep working for years.
- No
venv/pip/poetry/uvmess; share a script, recipient just needsgo. - Built-in formatting/linting/tooling, no
pyproject.tomlorpackage.jsonneeded. - Other compiled languages fit less well: Rust compiles slowly and has a thin stdlib that pushes you to dependencies.
gopls/gofmt insists on a space after //, rewriting //usr/... → // usr/..., which breaks the trick.
Fix (from the HN thread): use a block comment instead. Note the trailing ; on exit;:
/*usr/local/go/bin/go run "$0" "$@"; exit; */
package main
import "fmt"
func main() {
fmt.Println("Hello world")
}POSIX shebang handling (and the ENOEXEC fallback) is more flexible than people assume — worth experimenting with for ergonomic ways to run code.