CLI: args, flags, env
| os.Args | |
| The flag package | |
| Environment variables | |
| Exit codes |
os.Args
os.Args holds every word from the command line, starting with the program name itself. Real arguments begin at index one. Small tools can loop over them with no libraries at all. Print a usage line when required words are missing.
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Println("usage: greet NAME")
return
}
for i, arg := range os.Args[1:] {
fmt.Println(i+1, arg)
}
}
go run main.go alpha beta
1 alpha
2 beta
Index zero names the binary, so user input starts at one. range counts from zero, hence the plus one for human-friendly numbers. Anything fancier than this calls for the flag package.
The flag package
The flag package parses named options with defaults and help text built in. Declare each flag, call Parse once, then read the pointers. Users get a free -h summary describing every option. This covers most real command line tools.
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("name", "world", "who to greet")
times := flag.Int("n", 1, "how many times")
flag.Parse()
for i := 0; i < *times; i++ {
fmt.Println("hello,", *name)
}
}
go run main.go -name Ada -n 2
hello, Ada
hello, Ada
Flags come back as pointers, hence the stars when reading them. Both single dash and double dash forms work the same. Leftover positional words land in flag.Args after parsing.
Environment variables
Environment variables carry secrets and per-machine settings outside the binary. Getenv reads a value with an empty default, while LookupEnv tells missing apart from empty. Fall back to a sane default for local runs. Twelve-factor apps configure almost everything this way.
package main
import (
"fmt"
"os"
)
func main() {
port, ok := os.LookupEnv("PORT")
if !ok || port == "" {
port = "8080"
}
fmt.Println("listening on", port)
}
PORT=9090 go run main.go
listening on 9090
Setting the variable inline changes one run without touching your shell. Unset PORT prints 8080 instead. Never commit real secrets next to code that reads them.
Exit codes
Exit codes tell scripts whether the run succeeded. Zero means success and any other number signals failure. Write the reason to standard error first, so output stays clean for piping. Remember that deferred calls are skipped after Exit.
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "missing file argument")
os.Exit(2)
}
fmt.Println("reading", os.Args[1])
}
go run main.go; echo $?
missing file argument
2
Code 2 follows the classic shell hint for misuse of a command. Zero, one, and two cover nearly every script need. Reserve larger numbers for documented cases only.
Next: Logging
Article author: Arthur Isaev