os.Args
os.Args ist ein Slice von Strings. os.Args[0] ist der Name des Programms, der Rest sind die Argumente genau so, wie die Shell sie übergeben hat.
Im Args-Panel des Editors ist jedes Feld ein Argument und wird unverändert übergeben. Trag hello, two words und -v in drei Felder ein: Das Programm sieht drei Argumente, und two words bleibt ein Argument mit einem Leerzeichen darin. Im Terminal übernimmt die Shell das Aufteilen, dort schreibst du dasselbe als go run . hello "two words" -v.
Prüf immer len(os.Args), bevor du indizierst. os.Args[1] ohne Argumente löst eine Panic mit index out of range [1] with length 1 aus.
Argumente sind Strings. Konvertiere Zahlen mit strconv.Atoi oder strconv.ParseFloat und behandle den Fehler, denn Nutzer tippen alles Mögliche.
Das Paket flag
Für Optionen wie -port 8080 -verbose nimmst du flag. Es parst, konvertiert Typen, meldet Fehler und erzeugt eine Hilfemeldung.
Ohne Argumente gibt das einmal hello, world aus. Trag im Args-Panel -name und Gopher in zwei Felder ein, dann -count=3, -loud und extra in drei weitere. Das Programm gibt dreimal HELLO, GOPHER! aus und remaining args: [extra].
So funktionieren Flags:
- Jede Definitionsfunktion (
flag.String,flag.Int,flag.Bool,flag.Float64,flag.Duration,flag.Uint64...) nimmt einen Namen, einen Standardwert und einen Hilfetext und gibt einen Pointer zurück. Den Wert liest du nachflag.Parse()mit*name. - Die
Var-Formen binden an eine Variable, die du schon hast:flag.IntVar(&cfg.Port, "port", 8080, "port"). Für ein Config-Struct ist das aufgeräumter. - Nutzer können
-name value,-name=value,--name valueoder--name=valueschreiben. Go unterscheidet nicht zwischen einem und zwei Bindestrichen. - Booleans brauchen
=, um einen Wert zu nehmen.-loudsetzt true,-loud=falsesetzt false, aber-loud falsesetzt true und lässtfalseals Positionsargument übrig. - Das Parsen stoppt beim ersten Argument, das kein Flag ist (oder bei
--).prog file.txt -vbehandelt-vals Positionsargument. Setz Flags an den Anfang. flag.Args()gibt die verbleibenden Positionsargumente zurück,flag.NArg()ihre Anzahl undflag.Arg(i)eines davon.
Ein unbekanntes Flag oder ein falscher Wert gibt einen Fehler plus die Hilfe aus und beendet das Programm mit Status 2. -h oder -help gibt die Hilfe aus und beendet mit Status 0 (seit Go 1.15). Der Hilfetext wird aus deinen Definitionen erzeugt:
Usage of greet:
-count int
how many times (default 1)
-delay duration
pause between greetings, e.g. 10ms
-loud
shout the greeting
-name string
who to greet (default "world")
Setz flag.Usage auf eine Funktion, um deinen eigenen Kopf auszugeben, bevor du flag.PrintDefaults() aufrufst.
Unterbefehle mit FlagSet
Tools wie git commit -m msg haben Unterbefehle mit eigenen Flags. Leg pro Unterbefehl ein flag.FlagSet an und wähl eins mit einem switch auf das erste Argument:
flag.ContinueOnError lässt Parse einen Fehler zurückgeben, statt das Programm zu beenden, und hält die Funktion so testbar. run nimmt die Argumente als Parameter, statt os.Args zu lesen, also kann ein Test direkt run([]string{"list", "-all"}) aufrufen. Probier list und -all im Args-Panel aus, oder delete, um den Fehlerpfad zu sehen.
Für große CLIs mit verschachtelten Befehlen, Shell-Vervollständigung und generierter Doku nutzen die meisten Projekte das Drittanbieterpaket github.com/spf13/cobra. Kleine Tools deckt das Standardpaket flag gut ab.
Umgebungsvariablen
os.Getenvgibt""zurück, sowohl wenn die Variable fehlt als auch wenn sie auf einen leeren String gesetzt ist.os.LookupEnvunterscheidet die beiden Fälle.- Werte sind immer Strings. Konvertiere und validiere sie beim Start und brich mit einer klaren Meldung ab, statt mitten in einem Request zu scheitern.
os.Setenvwirkt auf den aktuellen Prozess und auf später gestartete Kindprozesse. Die Umgebung der Shell, die dich gestartet hat, kann es nicht ändern.os.Environ()gibt alle Variablen als Strings der Form"KEY=value"zurück.
Eine verbreitete Aufteilung für Konfiguration: Flags für alles, was ein Mensch eintippt, Umgebungsvariablen für Deployment-Einstellungen (Container-Plattformen setzen sie), wobei Flags die Umgebung überschreiben und die Umgebung die Standardwerte.
Exit-Codes und os.Exit
Ein Go-Programm beendet sich mit Status 0, wenn main zurückkehrt. os.Exit(code) beendet den Prozess sofort mit diesem Status. Per Konvention bedeutet 0 Erfolg, 1 einen allgemeinen Fehler und 2 einen Bedienungsfehler (das Paket flag verwendet 2).
os.Exit führt keine defer-Funktionen aus. Dateien werden nicht geschrieben, und das Aufräumen per defer entfällt. log.Fatal ruft os.Exit(1) auf und hat dieselbe Wirkung. Halte os.Exit an einer Stelle, am Ende von main, und steck das eigentliche Programm in eine Funktion run, die einen Fehler zurückgibt, so wie im Beispiel mit den Unterbefehlen:
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
Schreib Fehlermeldungen nach os.Stderr, nicht nach os.Stdout, damit sie sichtbar bleiben, wenn die Ausgabe in eine Datei umgeleitet oder an einen anderen Befehl weitergereicht wird. Eine nicht abgefangene Panic beendet das Programm mit Status 2.
Häufige Fehler
os.Argsindizieren, ohne die Länge zu prüfen. Fehlende Argumente lösen eine Panic aus.- Flags vor
flag.Parse()lesen. Du bekommst die Standardwerte. - Das
*vergessen.fmt.Println(port)gibt eine Adresse wie0xc000012345aus, nicht den Wert. - Flags hinter Positionsargumente setzen.
prog input.txt -vparst-vnicht. -verbose falsebei einem bool-Flag. Schreib-verbose=false.os.Exitoderlog.Fataltief im Programm aufrufen. Das Aufräumen per defer läuft nie, und der Code lässt sich nicht testen. Gib Fehler bis zumainzurück.
Häufig gestellte Fragen
Wie bekomme ich in Go die Kommandozeilenargumente?
os.Args ist ein []string mit dem Programmnamen an Index 0 und den Argumenten danach. os.Args[1:] sind die Argumente, die der Nutzer eingegeben hat. Prüf len(os.Args) vor dem Indizieren, sonst löst das Programm eine Panic aus, wenn ein Argument fehlt.
Wie nutze ich das Paket flag in Go?
Flags deklarieren, flag.Parse() aufrufen, dann lesen: port := flag.Int("port", 8080, "port to listen on"), flag.Parse(), fmt.Println(*port). Die Funktionen geben Pointer zurück. Nutzer schreiben -port=9000, -port 9000 oder --port 9000, und -h gibt die generierte Hilfe aus.
Wie lese ich in Go eine Umgebungsvariable?
os.Getenv("HOME") gibt den Wert zurück oder einen leeren String, wenn die Variable nicht gesetzt ist. Um „nicht gesetzt“ von „leer gesetzt“ zu unterscheiden, nimm v, ok := os.LookupEnv("HOME"). os.Setenv ändert die Umgebung des aktuellen Prozesses und der Kindprozesse, die er danach startet.
Führt os.Exit in Go die defer-Funktionen aus?
Nein. os.Exit beendet den Prozess sofort mit dem angegebenen Statuscode, und per defer eingeplante Aufrufe laufen nicht. Gepufferte Ausgaben können also verloren gehen, und Dateien werden eventuell nicht geschrieben. Ein verbreitetes Muster ist func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) } }, mit der ganzen eigentlichen Arbeit und allen defers in run.