Menu

Golang Command Line Arguments: os.Args, flag and Env Vars

How a Go program reads its command line: os.Args, the flag package for typed options, subcommands with FlagSet, environment variables with os.Getenv and os.LookupEnv, and exit codes with os.Exit.

This page includes runnable editors - edit, run, and see output instantly.

os.Args

os.Args is a slice of strings. os.Args[0] is the program's name, the rest are the arguments exactly as the shell passed them.

In the editor's Args panel, each field is one argument, passed as is. Try hello, two words and -v in three fields: the program sees three arguments, and two words stays one argument with a space inside. In a terminal the shell does the splitting, so the same thing is go run . hello "two words" -v.

Always check len(os.Args) before indexing. os.Args[1] with no arguments panics with index out of range [1] with length 1.

Arguments are strings. Convert numbers with strconv.Atoi or strconv.ParseFloat and handle the error, since users type anything.

The flag package

For options like -port 8080 -verbose, use flag. It parses, converts types, reports errors and generates a help message.

With no arguments this prints hello, world once. In the Args panel, try -name and Gopher in two fields, then -count=3, -loud and extra as three more. The program prints HELLO, GOPHER! three times and remaining args: [extra].

How flags work:

  • Each definer (flag.String, flag.Int, flag.Bool, flag.Float64, flag.Duration, flag.Uint64...) takes a name, a default and a usage string, and returns a pointer. Read the value with *name after flag.Parse().
  • The Var forms bind to a variable you already have: flag.IntVar(&cfg.Port, "port", 8080, "port"). That is tidier for a config struct.
  • Users can write -name value, -name=value, --name value or --name=value. Go makes no distinction between one and two dashes.
  • Booleans need = to take a value. -loud sets true, -loud=false sets false, but -loud false sets true and leaves false as a positional argument.
  • Parsing stops at the first non-flag argument (or at --). prog file.txt -v treats -v as a positional argument. Put flags first.
  • flag.Args() returns the positional arguments that remain, flag.NArg() their count, and flag.Arg(i) one of them.

An unknown flag or a bad value prints an error plus the usage, and exits with status 2. -h or -help prints the usage and exits with status 0 (since Go 1.15). The usage text is generated from your definitions:

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")

Set flag.Usage to a function to print your own header before calling flag.PrintDefaults().

Subcommands with FlagSet

Tools like git commit -m msg have subcommands with their own flags. Create a flag.FlagSet per subcommand and pick one with a switch on the first argument:

flag.ContinueOnError makes Parse return an error instead of exiting, which keeps the function testable. run takes the arguments as a parameter rather than reading os.Args, so a test can call run([]string{"list", "-all"}) directly. Try list and -all in the Args panel, or delete to see the error path.

For large CLIs with nested commands, shell completion and generated docs, most projects use the third-party github.com/spf13/cobra. The standard flag package covers small tools well.

Environment variables

  • os.Getenv returns "" both when the variable is missing and when it is set to an empty string. os.LookupEnv tells them apart.
  • Values are always strings. Convert and validate them at startup, and fail with a clear message rather than halfway through a request.
  • os.Setenv affects the current process and child processes started later. It cannot change the environment of the shell that launched you.
  • os.Environ() returns all variables as "KEY=value" strings.

A common layout for configuration: flags for things a person types, environment variables for deployment settings (container platforms set them), with flags overriding the environment and the environment overriding defaults.

Exit codes and os.Exit

A Go program exits with status 0 when main returns. os.Exit(code) ends the process immediately with that status. By convention 0 is success, 1 a general error, and 2 a usage error (the flag package uses 2).

os.Exit does not run deferred functions. Files are not flushed and defer cleanup is skipped. log.Fatal calls os.Exit(1) and has the same effect. Keep os.Exit in one place, at the end of main, and put the real program in a run function that returns an error, as the subcommand example does:

func main() {
	if err := run(os.Args[1:]); err != nil {
		fmt.Fprintln(os.Stderr, "error:", err)
		os.Exit(1)
	}
}

Write error messages to os.Stderr, not os.Stdout, so they stay visible when output is redirected to a file or piped to another command. A panic that is not recovered exits with status 2.

Common mistakes

  • Indexing os.Args without checking its length. Missing arguments panic.
  • Reading flags before flag.Parse(). You get the defaults.
  • Forgetting the *. fmt.Println(port) prints an address like 0xc000012345, not the value.
  • Putting flags after positional arguments. prog input.txt -v does not parse -v.
  • -verbose false for a bool flag. Write -verbose=false.
  • Calling os.Exit or log.Fatal deep inside the program. Deferred cleanup never runs and the code cannot be tested. Return errors up to main.

Frequently Asked Questions

How do I get command line arguments in Go?

os.Args is a []string holding the program name at index 0 and the arguments after it. os.Args[1:] are the arguments the user typed. Check len(os.Args) before indexing, or the program panics when an argument is missing.

How do I use the flag package in Go?

Declare flags, call flag.Parse(), then read them: port := flag.Int("port", 8080, "port to listen on"), flag.Parse(), fmt.Println(*port). The functions return pointers. Users write -port=9000, -port 9000 or --port 9000, and -h prints the generated usage.

How do I read an environment variable in Go?

os.Getenv("HOME") returns the value, or an empty string if the variable is unset. To tell unset from set-to-empty, use v, ok := os.LookupEnv("HOME"). os.Setenv changes the environment of the current process and the child processes it starts afterwards.

Does os.Exit run deferred functions in Go?

No. os.Exit ends the process immediately with the given status code, and deferred calls do not run, so buffered output may be lost and files may not be flushed. A common pattern is func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) } }, with all real work and defers inside run.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED