- Vorstellung eines Tricks, mit dem sich Go-Dateien direkt wie ausführbare Dateien ausführen lassen
- Wenn man in die erste Zeile
//usr/local/go/bin/go run "$0" "$@"; exit schreibt und die Datei ausführbar macht, kann man sie mit ./script.go starten
- Diese Methode ist kein Shebang, sondern nutzt das Verhalten von POSIX, bei ENOEXEC auf
/bin/sh zurückzufallen
- Die Shell führt die erste Zeile als Befehl aus, während der Go-Compiler sie als
//-Kommentar erkennt und ignoriert
- Mit
"$0" wird der Pfad zur Datei selbst übergeben, sodass go run das Skript baut und ausführt, und mit $@ werden die Argumente weitergereicht
- Gos starke Standardbibliothek und garantierte Abwärtskompatibilität machen es gut für Skripting geeignet; solange man Go 1.x verwendet, können Skripte über Jahrzehnte hinweg laufen
- Man kann die Komplexität der Abhängigkeitsverwaltung mit virtuellen Python-Umgebungen, pip/poetry/uv usw. vermeiden
Wie der falsche Shebang funktioniert
- Shebangs (
#!) geben den Interpreter über den Systemaufruf execve an, aber die hier vorgestellte Technik ist kein Shebang
- In die erste Zeile der Go-Quelldatei kommt
//usr/local/go/bin/go run "$0" "$@"; exit, darunter ab package main ganz normaler Go-Code
- Mit
chmod +x script.go erhält die Datei Ausführungsrechte und lässt sich als ./script.go starten
- Mit
strace sieht man, dass die Shell beim Versuch, ./script.go per execve auszuführen, vom Kernel ENOEXEC (Exec format error) zurückbekommt
- Nach ENOEXEC verwendet die Shell als Fallback
/bin/sh und interpretiert die Datei als Shell-Skript
- In der Shell ist
// kein Kommentar, sondern wird als Root-Pfad (/) interpretiert, daher lässt sich //usr/local/go/bin/go als regulärer Pfad ausführen
- Deshalb wird die erste Zeile
//usr/local/go/bin/go run "$0" "$@"; exit von der Shell als Befehl ausgeführt
"$0" übergibt den Pfad der ausgeführten Datei; im Aufruf wird "$0" also zum Pfad von script.go, sodass go run die Datei selbst findet, baut und ausführt
"$@" ist die Expansion der Positionsparameter ab dem ersten Argument und ermöglicht Aufrufe wie ./script.go -f flag0 here are some args
- Ohne
; exit würde sh die Go-Datei zeilenweise weiter interpretieren und bei Tokens wie package mit einem Fehler abbrechen
Warum Go sich gut für Skripting eignet
- Gos garantierte Abwärtskompatibilität ist die Schlüsselfunktion: Solange Go 1.x verwendet wird, laufen geschriebene Skripte langfristig weiter
- Die gut ausgebaute Standardbibliothek und integrierte Werkzeuge (Formatter, Linter usw.) werden ohne zusätzliche Konfiguration mitgeliefert, wodurch Teilen und Portabilität von Skripten maximiert werden
- Anders als bei Python muss man keine virtuellen Umgebungen oder verschiedene Paketmanager (pip, poetry, uv) erlernen, um Code auszuführen
- Durch die eingebauten Werkzeuge des Go-Ökosystems und die IDE-Integration lassen sich Formatter und Linter auch ohne
.pyproject oder package.json standardmäßig nutzen
- Wenn ein aktuelles Go installiert ist, lässt sich das Ganze auf jedem OS über Jahrzehnte hinweg ausführen
Vergleich mit anderen kompilierten Sprachen
- Rust kompiliert langsamer und hat eine schwächere Standardbibliothek, weshalb Abhängigkeiten praktisch unvermeidlich sind; zudem verlangsamt der Perfektionsanspruch die Entwicklung
- Java und JVM-Sprachen haben bereits Skriptsprachen auf JVM-Bytecode-Basis, und leichtgewichtiges Kotlin-Skripting kann ebenfalls eine Alternative sein
- Unter den kompilierten Sprachen besitzt Go die am besten geeigneten Eigenschaften für Skripting
Problem mit gopls-Formatierung und die Lösung
gopls verlangt ein Leerzeichen nach Kommentaren (//example → // example), wodurch die falsche Shebang-Zeile kaputtgeht
- Mit dem zusätzlichen Leerzeichen wird aus
//usr/local/go/bin/go nämlich // usr/local/go/bin/go, was die Shell nicht mehr als Pfad erkennt
- Lösung: Ein Vorschlag aus dem HN-Thread, bei dem statt
// ein /**/-Blockkommentar verwendet wird
- geschrieben als
/*usr/local/go/bin/go run "$0" "$@"; exit; */
- Das Semikolon (
;) nach exit ist Pflicht
Noch keine Kommentare.