Menu

Argumenty wiersza poleceń w Go: os.Args, flag, zmienne środowiskowe

Jak program w Go czyta wiersz poleceń: os.Args, pakiet flag do typowanych opcji, podkomendy z FlagSet, zmienne środowiskowe przez os.Getenv i os.LookupEnv oraz kody wyjścia przez os.Exit.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

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 *name po flag.Parse().
  • Wersje Var wiążą 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 value lub --name=value. Go nie rozróżnia jednego i dwóch myślników.
  • Flagi logiczne potrzebują =, żeby przyjąć wartość. -loud ustawia true, -loud=false ustawia false, ale -loud false ustawia true i zostawia false jako argument pozycyjny.
  • Parsowanie zatrzymuje się na pierwszym argumencie, który nie jest flagą (albo na --). prog file.txt -v traktuje -v jako argument pozycyjny. Flagi podawaj na początku.
  • flag.Args() zwraca pozostałe argumenty pozycyjne, flag.NArg() ich liczbę, a flag.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.Getenv zwraca "" zarówno wtedy, gdy zmiennej brakuje, jak i wtedy, gdy jest ustawiona na pusty string. os.LookupEnv rozróż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.Setenv wpł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.Args bez 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 typu 0xc000012345, a nie wartość.
  • Flagi po argumentach pozycyjnych. prog input.txt -v nie sparsuje -v.
  • -verbose false dla flagi logicznej. Pisz -verbose=false.
  • Wywoływanie os.Exit lub log.Fatal głęboko w programie. Odroczone sprzątanie nigdy się nie wykona, a kodu nie da się testować. Zwracaj błędy w górę, do main.

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.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ