23 Punkte von GN⁺ 2025-12-31 | 1 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

1 Kommentare

 
GN⁺ 2025-12-31
Hacker-News-Kommentare
  • Der Punkt des Autors, er wolle sich nicht mit „pip vs poetry vs uv“ beschäftigen, wird eigentlich von uv direkt abgedeckt
    Einschließlich PyPI-Abhängigkeiten reicht es, wenn die Python-Version und uv installiert sind
    Link zur offiziellen uv-Dokumentation

    • Es gibt sogar eine noch bessere Methode
      #!/usr/bin/env -S uv run --python 3.14 --script
      Damit lädt uv die angegebene Version herunter und führt sie aus, auch wenn Python selbst nicht installiert ist
    • Das dachte ich auch, aber für Nicht-Python-Nutzer ist das immer noch nicht intuitiv
      Wer zum ersten Mal mit Clojure in Berührung kommt, hört meist den Rat, Leiningen zu verwenden, bei Python findet man bei einer Suche dagegen venv, poetry, hatch, uv und vieles mehr
      uv wird zwar zunehmend zum De-facto-Standard, ist aber noch nicht allgemein verbreitet
      Ich habe früher einmal Go per apt installiert und dann wegen einer viel zu alten Version noch einmal neu installiert, aber das ließ sich deutlich schneller lösen
      Das Problem mit virtuellen Umgebungen in Python bleibt weiterhin komplex
    • Ich hatte dieses Problem 2019 mit PyFlow gelöst
      Ein in Rust geschriebenes OSS-Tool, das Python-Versionen und venv automatisch verwaltet
      Man konfiguriert einfach pyproject.toml und führt pyflow main.py aus; dann installiert und sperrt es Abhängigkeiten wie Cargo und gleicht auch automatisch die passende Python-Version für das Projekt an
      Damals waren Poetry und Pipenv populär, aber bei venv und Versionsverwaltung noch unzureichend
    • Ich bin inzwischen auch größtenteils zu uv gewechselt
      Meist nutze ich uv add, und nur bei Bedarf uv pip
      Allerdings hat uv pip weiterhin die gleichen Grenzen wie pip — die Auflösung von Abhängigkeiten hängt von der Installationsreihenfolge ab
      uv pip install dep-a und danach dep-b zu installieren ist etwas anderes als die Reihenfolge umzudrehen oder beides auf einmal zu installieren
      Das ist eher ein Problem von pip, aber das Chaos der Python-Paketverwaltung besteht weiter
    • Eigentlich muss man nicht einmal die Python-Version festlegen
      uv lädt sie selbst herunter
  • Go hat Shebang-Unterstützung ausdrücklich abgelehnt
    Stattdessen wird empfohlen, gorun zu verwenden
    Mit einem POSIX-Trick wie /// 2>/dev/null ; gorun "$0" "$@" ; exit $? lässt sich das ausführen
    Nim, Zig und D lassen sich mit der Option -run ähnlich verwenden, und Swift, OCaml und Haskell können Dateien direkt ausführen
    Link zur zugehörigen Diskussion

    • Für kleine Skripte könnte der yaegi-Interpreter besser geeignet sein als go run
      yaegi auf GitHub
  • Der Beitrag „Ich will den Unterschied zwischen pip, poetry und uv nicht kennen, ich will einfach nur Code ausführen“ ist letztlich ein Problem des technischen Kenntnisstands
    uv run und PEP 723 haben alle Probleme bereits gelöst

    • Stimmt, aber es hat viel zu lange gedauert, bis uv run kam
      Ich nutze Python seit über 20 Jahren, hatte aber immer Angst vor Codebasen mit externen Paketen oder venv
      Dank uv run habe ich alle Firmenprojekte umgestellt, aber bei privaten Projekten bin ich schon zu Go gewechselt
      Langfristig bevorzuge ich statisch typisierte Sprachen
    • Bei einer alten Sprache lernt man am Ende ohnehin konkurrierende Bibliotheken kennen
    • Das ist ein UX-Problem
      Nutzer wollen einfach, dass das Programm läuft
      uv run und PEP 723 lösen das Problem, aber weil man uv erst kennen muss, bleibt die Einstiegshürde bestehen
      Solange uv nicht das offizielle Standardwerkzeug ist, werden viele Nutzer Python verlassen
  • Ich halte das für eine wirklich geniale Idee
    Aber Scripting braucht ein anderes ergonomisches Gefühl als Software für die Auslieferung
    bash ist improvisierter, Go eignet sich eher für Produktisierung, Python liegt dazwischen, Ruby ist näher an bash und Rust eher auf der Go-Seite
    Skripte sind nützlich, wenn man OS-Befehle schnell für einmalige Aufgaben kombinieren will
    Go fehlt diese Improvisiertheit

    • Ich stimme auch zu, dass Python „irgendwo dazwischen“ liegt
      Ich wollte unter Debian eine einfache gtk-App mit uv ausführen; obwohl alle Abhängigkeiten stimmten, lief sie nicht und endete schließlich in einem Core Dump
      Jedes Mal, wenn ich Python erneut ausprobiere, passiert so etwas
      Go ist zwar umständlicher, funktioniert aber nach dem Kompilieren einfach
    • Ich empfinde das ähnlich
      Der Kernpunkt ist, ob es in einer einzigen Datei abgeschlossen werden kann
      Auch in Go sind 500-Zeilen-Skripte möglich, aber die Sprache selbst geht von mehreren Dateien und Modulen aus
      Dass die bang-line nicht funktioniert, passt ebenfalls dazu
      Wenn go run ohnehin temporäre Binärdateien erzeugt, halte ich es für besser, gleich zu bauen und nach /usr/local/bin zu legen
    • Die Aussage, bash sei näher an OS-Befehlen, ist ein Missverständnis
      bash ist genauso eine Abstraktionsschicht über dem OS wie Python; es wirkt nur so, weil es die Standard-Shell ist
    • In einer Zeit, in der LLMs Code anstelle von Menschen anpassen, könnte Lesbarkeit wichtiger werden als Schreibe-Ergonomie
      Vor allem in Richtung von Code, den Menschen trotz LLM-Erzeugung gut lesen können
  • Ich stimme zu, dass Nutzer, die Python zum ersten Mal sehen, den Unterschied zwischen pip, poetry und uv nicht kennen müssen
    Aber wenn jemand über so ein Thema bloggt, sollte er zumindest wissen, dass uv das Problem löst
    Uninformierte Kritik ist nicht überzeugend

    • Es gibt die Frage, ob uv wie Go „write once, run anywhere“ löst
      Ich habe das Konzept von uv selbst noch nicht ganz verstanden und bin deshalb neugierig
  • Ich schreibe gern Skripte in Python
    Man kann schnell etwas erledigen, und für einfache Aufgaben ist es gut, ohne sich um Typen oder Speicher kümmern zu müssen
    Für große Anwendungen würde ich es aber nicht verwenden

    • Ich mag Python-Scripting auch, aber ich hasse es, fremde Skripte zu installieren
    • Das ist ein Linux-zentrierter Ansatz
      Auf den meisten Systemen ist Python standardmäßig vorhanden, und für einfache Skripte reicht das aus
      Wenn man bedenkt, dass Go erst installiert werden muss, erscheint mir Python mit uv eher sinnvoll
      Wie der Autor selbst sagte, habe das Ganze „leicht trollig angefangen“, letztlich ist es also eine Frage der Go-Präferenz
    • Ich halte auch JS nicht für schlecht als Skriptsprache
      Mit node bla.js ist es erledigt
    • Um Typen muss man sich immer kümmern
      Man muss wissen, was eine Funktion zurückgibt, und wenn man die Sprache gut kennt, verarbeitet man Grundtypen aus dem Gedächtnis
      Das gilt für statisch typisierte Sprachen genauso
    • Python ist für Entwickler großartig, aber für Deployment oder Integration ein Albtraum
      Wenn man andere Leute mitdenken will, sollte man keinen Code für die Auslieferung in Python schreiben
  • Ich hatte eine Kritik an Python erwartet, aber stattdessen war es eher ein nützlicher Tipp
    Bei Sprachen, die // als Kommentar verwenden, lässt sich dieser Trick übernehmen
    Das geht mit C/C++, Java, JavaScript, Rust, Swift, Kotlin, ObjC, D, F#, GLSL und anderen
    Besonders interessant ist GLSL für Grafikdemos in einer einzigen Datei
    Shadertoy-Beispiel
    In C kann man mit Blockkommentaren auch etwas wie /*/../usr/bin/env gcc "$0" "$@"; ./a.out; rm -vf a.out; exit; */ verwenden

    • Für Swift gibt es das Projekt swift-sh, mit dem sich Skripte mit externen Abhängigkeiten ausführen lassen
      Das ist gewissermaßen ein uv für Swift
      Swift unterstützt Shebang auch offiziell
    • In C/C++ kann man #! direkt verwenden
      Zu alten TCC-Zeiten habe ich so etwas als „C-Scripting“ genutzt
      In größeren Projekten gab es eine Struktur, bei der ein Build-Skript das Manifest las, baute und danach ausführte
      Wegen der schwierigen Kontrolle über die Umgebung ist das für die Praxis aber ungeeignet
    • Rust braucht solche Tricks nicht
      Es unterstützt Shebang direkt
  • Wer eine ergonomischere Sprache möchte, kann auch die Funktion „run file directly“ in .NET 10 nutzen
    Sie unterstützt Shebang und installiert Pakete innerhalb des Skripts automatisch
    Mit der Direktive #:sdk kann man sogar Web-Apps sofort ausführen

    • Ich habe heute selbst zum ersten Mal mit dieser Funktion ein C#-Skript geschrieben, und das war eine ziemlich gute Erfahrung
      Nur die AOT-Kompilierung wirkt noch etwas roh
  • Zuerst dachte ich, das sei Python-Kritik, aber stattdessen hat es mich über die Richtung von Sprachökosystemen nachdenken lassen
    Dass ML an Python gebunden wurde, halte ich für einen großen Fehler
    Es ist langsam, sein Typsystem ist unpraktisch, und Deployment ist schwierig
    Jetzt sollte man Alternativen wie TypeScript, Go und Rust in Betracht ziehen

    • Stimme zu
      Der Grund, warum ML auf Python gesetzt hat, war allerdings die C-basierte FFI
      NodeJS, Rust und Go sind bei FFI schwach
      Python hat hier eine Stärke
      Ideal wäre eine Sprache, die so einfach wie Python ist, aber ein besseres Typsystem und ein besseres Deployment-Modell hat
    • Ich kann der Idee nicht zustimmen, es durch TypeScript zu ersetzen
      Ich möchte Python nicht durch eine Sprache ersetzen, die aus dem JS-Ökosystem kommt
    • Dass ML zu Python gegangen ist, lag am Marktdruck
      Lisp oder Lua (Torch) wären geeigneter gewesen, aber wegen der Einfachheit fiel die Wahl auf Python
      Ich entwickle selbst ein Lisp-basiertes ML-Framework, aber ich glaube nicht, dass die Akzeptanz leicht sein wird
    • Die Abhängigkeitshölle von Python ist weiterhin gravierend
      Versionskompatibilitätsprobleme, fehlendes semver und ein instabiles Ökosystem lassen es im Vergleich zu JS rückständig wirken
      JS/Node ist in den letzten zehn Jahren gereift, Python steckt dagegen immer noch im Jahr 2012 fest
      Dass ML auf Python standardisiert wurde, ist wirklich bedauerlich
    • Ich will eine einfache, ausdrucksstarke Sprache, die stark typisiert und nativ kompiliert wird
      Beim Erstellen von CLI-Tools ist Go viel schneller als Python
      Wegen des Unterschieds bei den LOC bin ich zwar wieder zu Python zurückgekehrt, aber jedes Mal beim Ausführen vermisse ich Go
      Vermutlich wäre OCaml ideal, aber die altmodischen Tooling-Werkzeuge sind belastend
  • Das Problem bei Go-Skripten ist, dass die erste Zeile keinen Leerraum enthalten darf
    Weil gopls automatische Formatierung erzwingt
    Auch in CI muss die Formatkonsistenz gewahrt bleiben, daher ist das in der Praxis wichtig
    Das größere Problem ist aber, dass man go.mod nicht verwenden kann
    Das heißt, man kann Abhängigkeitsversionen nicht festlegen, wodurch die Kompatibilitätsgarantie schwächer wird

    • Trotzdem werden Major-Versionen über den Import-Pfad fixiert, also ist es grundsätzlich kompatibel
    • Das ist ein Kompatibilitätsproblem auf Sprach-/Runtime-Ebene, nicht wirklich ein Abhängigkeitsproblem