ZetCode

Go command-line arguments

last modified October 30, 2026

In this article we show how to pass and parse command-line arguments in Go.

The os.Args slice holds the raw arguments: the first element is the name of the program, and the rest are the words typed after it. For anything more than a list of strings, the flag package in the standard library parses named options with types, defaults, help text and validation.

All examples in this article were compiled with Go 1.27.0 and run as built binaries, so the program name in the output is the name of the binary.

Reading os.Args

The example prints the program name, the number of arguments and their type, then greets every name on the command line.

main.go
package main

import (
    "fmt"
    "os"
    "reflect"
)

func main() {

    fmt.Println("program:", os.Args[0])
    fmt.Println("count:", len(os.Args)-1)
    fmt.Println("type:", reflect.TypeOf(os.Args[1:]))

    for i, name := range os.Args[1:] {
        fmt.Printf("%d: Hello, %s!\n", i+1, name)
    }
}

os.Args[0] is the path used to start the program, here ./args. The remaining elements form a []string, which the loop walks in order.

$ go build -o args main.go
$ ./args Jan Peter Lucia
program: ./args
count: 3
type: []string
1: Hello, Jan!
2: Hello, Peter!
3: Hello, Lucia!

Named flags with the flag package

The flag package declares options as typed values. Each call returns a pointer to the value, and flag.Parse fills it from the command line.

main.go
package main

import (
    "flag"
    "fmt"
)

func main() {

    name := flag.String("name", "guest", "the name to greet")
    age := flag.Int("age", 0, "the age of the person")
    verbose := flag.Bool("v", false, "verbose output")

    flag.Parse()

    fmt.Printf("name=%s age=%d\n", *name, *age)

    if *verbose {
        fmt.Println("verbose mode is on")
    }
}

String, integer and boolean flags are declared with flag.String, flag.Int and flag.Bool. The boolean flag can be used without a value, and the pointer dereference supplies the parsed value.

$ go build -o flags main.go
$ ./flags -name Jan -age 33 -v
name=Jan age=33
verbose mode is on

Defaults and explicitly set flags

Every flag declaration includes a default value. flag.Visit walks only the flags that appeared on the command line, which is useful for distinguishing a default from a value the user supplied.

main.go
package main

import (
    "flag"
    "fmt"
)

func main() {

    name := flag.String("name", "guest", "the name to greet")
    flag.Parse()

    flag.Visit(func(f *flag.Flag) {
        fmt.Println("explicitly set:", f.Name)
    })

    fmt.Println("name:", *name)
}

Only name was set, so it is the only flag that flag.Visit reports. Without arguments the program would still print name: guest, because the default is used when the flag is absent.

$ go build -o visit main.go
$ ./visit -name Jan
explicitly set: name
name: Jan

Positional arguments

Arguments that are not flags are available through flag.Args. Parsing stops at the first non-flag argument, so flags placed after the first positional argument are treated as positional values.

main.go
package main

import (
    "flag"
    "fmt"
)

func main() {

    n := flag.Int("n", 1, "how many times to print")
    flag.Parse()

    args := flag.Args()
    fmt.Println("positional:", args, len(args))

    for range *n {
        fmt.Println("falcon")
    }
}

The flag -n is parsed first, and falcon and sky are left in flag.Args(). The loop prints the word twice.

$ go build -o pos main.go
$ ./pos -n 2 falcon sky
positional: [falcon sky] 2
falcon
falcon

Requiring a flag

The flag package has no built-in required flags. The usual approach is to use an empty default and check the value after parsing, then print an error and exit with a non-zero status.

main.go
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {

    name := flag.String("name", "", "the name to greet (required)")
    flag.Parse()

    if *name == "" {
        fmt.Fprintln(os.Stderr, "error: -name is required")
        os.Exit(2)
    }

    fmt.Println("hello", *name)
}

Without -name the program writes the error to standard error and exits with status 2. The shell prompt reports the exit status shown above.

$ go build -o app main.go
$ ./app; echo "exit: $?"
error: -name is required
exit: 2

Subcommands

A program with subcommands can create a separate flag.FlagSet for each one. The first argument selects the subcommand, and the rest are parsed by its flag set.

main.go
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {

    if len(os.Args) < 2 {
        fmt.Println("usage: tasks <add|list>")
        os.Exit(1)
    }

    switch os.Args[1] {
    case "add":
        fs := flag.NewFlagSet("add", flag.ExitOnError)
        title := fs.String("title", "", "task title")
        fs.Parse(os.Args[2:])
        fmt.Printf("added %q\n", *title)

    case "list":
        fmt.Println("no tasks yet")

    default:
        fmt.Fprintf(os.Stderr, "unknown command %q\n", os.Args[1])
        os.Exit(1)
    }
}

The add subcommand has its own -title flag. A flag set created with flag.NewFlagSet parses only the arguments it is given, so the parent options and the subcommand options do not interfere.

$ go build -o tasks main.go
$ ./tasks add -title "buy milk"
added "buy milk"

Environment variable fallback

Flags can fall back to environment variables by computing their default value before flag.Parse runs. An explicit flag then overrides the environment.

main.go
package main

import (
    "flag"
    "fmt"
    "os"
    "strconv"
)

func envInt(key string, def int) int {
    if v := os.Getenv(key); v != "" {
        if n, err := strconv.Atoi(v); err == nil {
            return n
        }
    }
    return def
}

func main() {

    port := flag.Int("port", envInt("PORT", 8080), "port to listen on")
    flag.Parse()

    fmt.Println("port:", *port)
}

envInt reads PORT and uses it as the default, so the program prints 9090. Running ./env -port 3000 would print 3000 and leave the environment untouched.

$ go build -o env main.go
$ PORT=9090 ./env
port: 9090

Duration flags

flag.Duration parses strings such as 1500ms or 1h30m into a time.Duration. Arithmetic on durations works without manual conversion.

main.go
package main

import (
    "flag"
    "fmt"
    "time"
)

func main() {

    timeout := flag.Duration("timeout", 5*time.Second, "request timeout")
    flag.Parse()

    fmt.Println("timeout:", *timeout)
    fmt.Println("half:", *timeout/2)
}

The parsed value is 1.5 seconds, and dividing it by two gives 750 milliseconds. The default of 5 seconds is used when the flag is omitted.

$ go build -o app main.go
$ ./app -timeout 1500ms
timeout: 1.5s
half: 750ms

Repeatable flags

A flag that can appear several times needs a custom type that implements flag.Value, that is, a String method and a Set method. flag.Var registers it.

main.go
package main

import (
    "flag"
    "fmt"
    "strings"
)

type stringList []string

func (s *stringList) String() string {
    return strings.Join(*s, ",")
}

func (s *stringList) Set(v string) error {
    *s = append(*s, v)
    return nil
}

func main() {

    var tags stringList
    flag.Var(&tags, "tag", "tag to attach (repeatable)")
    flag.Parse()

    fmt.Println(tags, len(tags))
}

Every -tag appends one value to the slice, so the program collects two tags. The String method supplies the value shown in the usage message.

$ go build -o app main.go
$ ./app -tag go -tag cli
[go cli] 2

Parsing numbers from os.Args

When the arguments are numbers rather than named flags, convert them with strconv.Atoi and report invalid input. The example sums all arguments and stops at the first one that is not an integer.

main.go
package main

import (
    "fmt"
    "os"
    "strconv"
)

func main() {

    sum := 0

    for _, arg := range os.Args[1:] {
        n, err := strconv.Atoi(arg)
        if err != nil {
            fmt.Fprintf(os.Stderr, "%q is not a number\n", arg)
            os.Exit(1)
        }
        sum += n
    }

    fmt.Println("sum:", sum)
}

The three arguments are converted and added. With ./sum 10 x the program would print "x" is not a number and exit with status 1.

$ go build -o sum main.go
$ ./sum 10 20 30
sum: 60

Unknown flags

If the program uses the default flag.CommandLine set, an unknown flag is an error: the package prints the problem together with the usage text and exits with status 2.

main.go
package main

import (
    "flag"
    "fmt"
)

func main() {

    verbose := flag.Bool("v", false, "verbose output")
    flag.Parse()

    fmt.Println("verbose:", *verbose)
}

The usage listing is generated from the registered flags. The exit status is 2, the convention used by the flag package for command-line mistakes.

$ go build -o app main.go
$ ./app -x; echo "exit: $?"
flag provided but not defined: -x
Usage of ./app:
  -v	verbose output
exit: 2

Source

Go os package - reference

Go flag package - reference

In this article we have shown how to read os.Args, declare string, integer, boolean, duration and repeatable flags, use defaults, positional arguments, required flags, subcommands, environment fallbacks and manual number parsing, and how the flag package reports unknown options.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Go tutorials.