This guide is for Go beginners who need to read golang command line arguments from the shell: raw os.Args, safe indexing, the flag package for named options, parsing strings into numbers, and how go run forwards arguments. For iterating argument slices, see iterate over array or slice.
Tested with Go 1.24 on Linux.
Quick answer: os.Args and flags
Command-line arguments in Go are available as the string slice os.Args from the os package. os.Args[0] is the program name or path; os.Args[1:] holds everything the user typed after that. For named options such as -port 8080, use the standard library flag package instead of hand-parsing os.Args.
What are command line arguments?
When you run go run . read write or ./mybin --help, the shell splits the line into tokens. The Go runtime copies them into os.Args. Your program does not receive typed numbers or booleans—only strings—so you convert with strconv or flag helpers when needed.
Read arguments with os.Args
Args is declared in package os as var Args []string. The first element is always present and identifies how the program was invoked; additional elements depend on what the user passed.
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("full slice:", os.Args)
fmt.Println("program (Args[0]):", os.Args[0])
if len(os.Args) > 1 {
fmt.Println("user args (Args[1:]):", os.Args[1:])
} else {
fmt.Println("no user arguments (only program name)")
}
}In the in-browser Run environment you may see only the program path; on your machine, go run . foo bar adds foo and bar to os.Args[1] and os.Args[2].
Always check len(os.Args) before using os.Args[1], os.Args[2], and so on—otherwise a user who runs the binary with no extras triggers an index-out-of-range panic.
go run vs a compiled binary
| How you run | What appears in os.Args[0] |
|---|---|
go run . a b |
Compiler-generated temp path or similar; a, b still land in os.Args[1:]. |
./mybin a b |
Path you used to invoke ./mybin; a, b in os.Args[1:]. |
Arguments always belong to your program token list; they are not “inside” the go run subcommand once you place them after the package or file list, for example go run . -- -config when you need a literal -- passed through—use -- after go run options if your tool must see -- itself (rare for small programs).
Positional arguments and validation
Positional args are the non-flag tokens in order: file paths, subcommand names, or IDs. Validate len(os.Args) against the minimum you require and print a short usage line when the count is wrong.
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, "usage: %s <filename>\n", os.Args[0])
os.Exit(1)
}
file := os.Args[1]
fmt.Println("would open:", file)
}Run locally with go run . myfile.txt; without an extra argument it should print usage: to stderr and exit with code 1. The Run button may only show the usage path when no file is passed.
Command line flags with the flag package
Use flag for named options (-name value), defaults, and built-in -h style help. Call flag.Parse() after declaring flags, then read pointer values or use flag.Args() for remaining positional tokens.
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("name", "guest", "who to greet")
verbose := flag.Bool("v", false, "verbose output")
port := flag.Int("port", 8080, "listen port")
flag.Parse()
fmt.Printf("hello %s on port %d\n", *name, *port)
if *verbose {
fmt.Println("verbose enabled")
}
if flag.NArg() > 0 {
fmt.Println("extra positional:", flag.Args())
}
}You can try Run with no arguments (defaults apply) or imagine go run . -name=Ada -v extra.txt where extra.txt becomes a positional argument in flag.Args() after Parse.
Parsing numbers and booleans from strings
Raw os.Args entries are strings. Use strconv.Atoi, ParseBool, or flag.Int / flag.Bool so invalid input returns an error instead of silently mis-parsing.
package main
import (
"fmt"
"os"
"strconv"
)
func main() {
if len(os.Args) != 2 {
fmt.Println("usage: program <integer>")
return
}
n, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Println("invalid integer:", err)
return
}
fmt.Println("double:", n*2)
}Run locally as go run . 21 to see double: 42; with go run . x you should see an invalid integer message.
os.Args vs flag: when to use which
| Need | Approach |
|---|---|
| Quick script, one or two positional values | os.Args + length checks |
| Named options, defaults, usage text | flag |
| Mix of both | flag.Parse() then flag.Args() for positionals |
| Many subcommands and nested flags | Consider Cobra or another library |
Keep simple CLIs on the standard library until complexity grows.
Real-world shapes (brief)
| CLI shape | Pattern |
|---|---|
| Input file path | First positional after flags, or -f path |
| Environment name | flag.String("env", "dev", ...) |
| Port | flag.Int("port", 8080, ...) |
| Verbose | flag.Bool("v", false, ...) |
| Subcommand style | First positional os.Args[1] as command name, or use Cobra |
When to use Cobra or another CLI library
The flag package fits single-command tools and a handful of options.
For example, these are good candidates for the standard flag package:
backup --src /data --dest /mnt/backup
server --port 8080 --debug
convert --input app.log --output app.jsonThese commands have one main action and a small set of options.
A CLI library such as Cobra, urfave/cli, or Kong becomes more useful when your tool grows into many commands and subcommands:
mytool db migrate
mytool db rollback
mytool user create
mytool user delete
mytool config set
mytool config getAt that point, you may want:
| Requirement | Better fit |
|---|---|
| One command with few flags | flag package |
| Many subcommands | Cobra or similar library |
| Shared flags across commands | Cobra or similar library |
| Generated help for many commands | Cobra or similar library |
| Shell completion support | Cobra, urfave/cli v2, Kong, or similar |
Short rule:
Start with flag for small tools.
Move to a CLI library only when subcommands, shared flags, and help text across many commands justify the extra structure.Mistakes to avoid
Treating os.Args[1] as the “first argument” without checking length—panics on empty user args.
Forgetting that os.Args[0] is the program path, not user data.
Re-implementing -flag value parsing by hand when flag would be clearer and safer.
Assuming shell quoting is removed differently than it is—the Go program already receives the post-shell token list.
Using os.Args indices for flags that flag already parsed—prefer flag values after Parse.
Go command line arguments cheat sheet
| Goal | Approach |
|---|---|
| Raw tokens | os.Args |
| Skip program name | os.Args[1:] |
| Safe indexing | if len(os.Args) > n { ... } |
| Named option | flag.String, flag.Int, flag.Bool, etc. |
Defaults and -h |
flag package |
| Positionals after flags | flag.Args() after flag.Parse() |
| String to number | strconv.Atoi / ParseInt with errors |
go run with args |
go run . arg1 arg2 |
| Rich subcommands | Cobra or similar |
Summary
Golang command line arguments live in os.Args: index 0 is the program invocation, 1: are user tokens. Check length before indexing, convert types explicitly, and use the flag package when you want named options, defaults, and usage text. go run . forwards trailing tokens to your program the same way a compiled binary does. For large multi-command CLIs, graduate from flag to a dedicated library after the complexity is real.
References
- Package os — Args
- Package flag
- Package strconv
- Cobra (CLI framework for subcommands and completions)

