os.Args
os.Args to slice stringów. os.Args[0] to nazwa programu, reszta to argumenty dokładnie w takiej postaci, w jakiej przekazała je powłoka.
W panelu Args w edytorze każde pole to jeden argument, przekazany bez zmian. Wpisz hello, two words i -v w trzech polach: program zobaczy trzy argumenty, a two words pozostanie jednym argumentem ze spacją w środku. W terminalu dzieleniem zajmuje się powłoka, więc to samo wygląda tak: go run . hello "two words" -v.
Zawsze sprawdzaj len(os.Args) przed indeksowaniem. os.Args[1] bez argumentów wywołuje panic z komunikatem index out of range [1] with length 1.
Argumenty są stringami. Liczby konwertuj przez strconv.Atoi lub strconv.ParseFloat i obsługuj błąd, bo użytkownicy wpisują cokolwiek.
Pakiet flag
Do opcji typu -port 8080 -verbose użyj flag. Pakiet parsuje, konwertuje typy, zgłasza błędy i generuje komunikat pomocy.
Bez argumentów program raz wypisuje hello, world. W panelu Args wpisz -name i Gopher w dwóch polach, a potem -count=3, -loud i extra w trzech kolejnych. Program trzy razy wypisze HELLO, GOPHER! oraz remaining args: [extra].
Jak działają flagi:
- Każda funkcja definiująca (
flag.String,flag.Int,flag.Bool,flag.Float64,flag.Duration,flag.Uint64...) przyjmuje nazwę, wartość domyślną i opis użycia, a zwraca wskaźnik. Wartość odczytujesz przez*namepoflag.Parse(). - Wersje
Varwiążą flagę z istniejącą zmienną:flag.IntVar(&cfg.Port, "port", 8080, "port"). To wygodniejsze przy strukturze konfiguracji. - Użytkownik może napisać
-name value,-name=value,--name valuelub--name=value. Go nie rozróżnia jednego i dwóch myślników. - Flagi logiczne potrzebują
=, żeby przyjąć wartość.-loudustawia true,-loud=falseustawia false, ale-loud falseustawia true i zostawiafalsejako argument pozycyjny. - Parsowanie zatrzymuje się na pierwszym argumencie, który nie jest flagą (albo na
--).prog file.txt -vtraktuje-vjako argument pozycyjny. Flagi podawaj na początku. flag.Args()zwraca pozostałe argumenty pozycyjne,flag.NArg()ich liczbę, aflag.Arg(i)jeden z nich.
Nieznana flaga lub zła wartość wypisuje błąd oraz instrukcję użycia i kończy program ze statusem 2. -h lub -help wypisuje instrukcję użycia i kończy program ze statusem 0 (od Go 1.15). Tekst instrukcji jest generowany z twoich definicji:
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")
Przypisz do flag.Usage funkcję, żeby wypisać własny nagłówek przed wywołaniem flag.PrintDefaults().
Podkomendy z FlagSet
Narzędzia takie jak git commit -m msg mają podkomendy z własnymi flagami. Utwórz flag.FlagSet dla każdej podkomendy i wybierz właściwą przez switch na pierwszym argumencie:
flag.ContinueOnError sprawia, że Parse zwraca błąd zamiast kończyć program, dzięki czemu funkcję da się testować. run przyjmuje argumenty jako parametr zamiast czytać os.Args, więc test może wywołać bezpośrednio run([]string{"list", "-all"}). Wpisz list i -all w panelu Args albo delete, żeby zobaczyć ścieżkę błędu.
W dużych CLI z zagnieżdżonymi komendami, uzupełnianiem w powłoce i generowaną dokumentacją większość projektów używa zewnętrznego github.com/spf13/cobra. Standardowy pakiet flag dobrze sprawdza się w małych narzędziach.
Zmienne środowiskowe
os.Getenvzwraca""zarówno wtedy, gdy zmiennej brakuje, jak i wtedy, gdy jest ustawiona na pusty string.os.LookupEnvrozróżnia te przypadki.- Wartości są zawsze stringami. Konwertuj je i sprawdzaj przy starcie, a w razie problemu kończ z jasnym komunikatem, zamiast w połowie obsługi żądania.
os.Setenvwpływa na bieżący proces i procesy potomne uruchomione później. Nie może zmienić środowiska powłoki, która uruchomiła program.os.Environ()zwraca wszystkie zmienne jako stringi"KEY=value".
Popularny układ konfiguracji: flagi dla rzeczy, które wpisuje człowiek, zmienne środowiskowe dla ustawień wdrożenia (ustawiają je platformy kontenerowe), przy czym flagi nadpisują środowisko, a środowisko nadpisuje wartości domyślne.
Kody wyjścia i os.Exit
Program w Go kończy się ze statusem 0, gdy main zwraca. os.Exit(code) natychmiast kończy proces z podanym statusem. Zgodnie z konwencją 0 oznacza sukces, 1 ogólny błąd, a 2 błąd użycia (pakiet flag używa 2).
os.Exit nie uruchamia odroczonych funkcji. Pliki nie zostają zapisane na dysk, a sprzątanie przez defer jest pomijane. log.Fatal wywołuje os.Exit(1) i działa tak samo. Trzymaj os.Exit w jednym miejscu, na końcu main, a właściwy program umieść w funkcji run, która zwraca błąd, tak jak w przykładzie z podkomendami:
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
Komunikaty o błędach zapisuj do os.Stderr, a nie do os.Stdout, żeby były widoczne, gdy wyjście jest przekierowane do pliku lub przekazane potokiem do innej komendy. Nieobsłużony panic kończy program ze statusem 2.
Typowe błędy
- Indeksowanie
os.Argsbez sprawdzenia długości. Brakujące argumenty wywołują panic. - Odczyt flag przed
flag.Parse(). Dostajesz wartości domyślne. - Zapomniana
*.fmt.Println(port)wypisuje adres typu0xc000012345, a nie wartość. - Flagi po argumentach pozycyjnych.
prog input.txt -vnie sparsuje-v. -verbose falsedla flagi logicznej. Pisz-verbose=false.- Wywoływanie
os.Exitlublog.Fatalgłęboko w programie. Odroczone sprzątanie nigdy się nie wykona, a kodu nie da się testować. Zwracaj błędy w górę, domain.
Najczęściej zadawane pytania
Jak odczytać argumenty wiersza poleceń w Go?
os.Args to []string, który pod indeksem 0 zawiera nazwę programu, a dalej argumenty. os.Args[1:] to argumenty wpisane przez użytkownika. Sprawdź len(os.Args) przed indeksowaniem, bo inaczej program wywoła panic, gdy argumentu zabraknie.
Jak używać pakietu flag w Go?
Zadeklaruj flagi, wywołaj flag.Parse(), a potem je odczytaj: port := flag.Int("port", 8080, "port to listen on"), flag.Parse(), fmt.Println(*port). Funkcje zwracają wskaźniki. Użytkownik pisze -port=9000, -port 9000 albo --port 9000, a -h wypisuje wygenerowaną instrukcję użycia.
Jak odczytać zmienną środowiskową w Go?
os.Getenv("HOME") zwraca wartość albo pusty string, jeśli zmienna nie jest ustawiona. Żeby odróżnić brak zmiennej od zmiennej ustawionej na pusty tekst, użyj v, ok := os.LookupEnv("HOME"). os.Setenv zmienia środowisko bieżącego procesu i procesów potomnych uruchomionych później.
Czy os.Exit uruchamia odroczone funkcje w Go?
Nie. os.Exit natychmiast kończy proces z podanym kodem statusu, a odroczone wywołania się nie wykonują, więc zbuforowane wyjście może przepaść, a pliki mogą nie zostać zapisane na dysk. Popularny wzorzec to func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) } }, z całą właściwą pracą i wszystkimi defer wewnątrz run.