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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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
List all Go tutorials.