23 Punkte von GN⁺ 2025-12-31 | Noch keine Kommentare. | Auf WhatsApp teilen
  • 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.

Noch keine Kommentare.