Command-Line Arguments in Golang

Command-Line Arguments in Golang

Command-line arguments allow you to pass data to your Go programs when you run them from the terminal. Go provides several ways to handle command-line arguments, from simple access to the raw arguments to more sophisticated flag parsing.

Basic Command-Line Arguments

The simplest way to access command-line arguments is through the os.Args variable.

basic-args.go
package main

import (
    "fmt"
    "os"
)

func main() {
    // os.Args[0] is the program name
    fmt.Println("Program name:", os.Args[0])
    
    // os.Args[1:] contains the actual arguments
    fmt.Println("Arguments:", os.Args[1:])
    
    // Number of arguments
    fmt.Println("Number of arguments:", len(os.Args)-1)
    
    // Process each argument
    for i, arg := range os.Args[1:] {
        fmt.Printf("Argument %d: %s\n", i+1, arg)
    }
}

Run this program:

go run basic-args.go hello world 123

Output:

Program name: /tmp/go-build123456789/b001/exe/basic-args
Arguments: [hello world 123]
Number of arguments: 3
Argument 1: hello
Argument 2: world
Argument 3: 123

Using the flag Package

The flag package provides a more sophisticated way to parse command-line flags.

flag-example.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    // Define flags
    var name string
    var age int
    var verbose bool
    
    // String flag
    flag.StringVar(&name, "name", "Anonymous", "Your name")
    
    // Int flag
    flag.IntVar(&age, "age", 0, "Your age")
    
    // Bool flag
    flag.BoolVar(&verbose, "verbose", false, "Verbose output")
    
    // Parse the flags
    flag.Parse()
    
    // Access flag values
    fmt.Printf("Name: %s\n", name)
    fmt.Printf("Age: %d\n", age)
    fmt.Printf("Verbose: %t\n", verbose)
    
    // Remaining arguments after flags
    fmt.Println("Remaining arguments:", flag.Args())
}

Usage examples:

go run flag-example.go -name "John Doe" -age 30 -verbose
go run flag-example.go -name Alice extra arg1 arg2

Different Flag Types

The flag package supports various data types:

flag-types.go
package main

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

func main() {
    // String flag
    var message = flag.String("message", "Hello", "a message to print")
    
    // Int flag
    var count = flag.Int("count", 1, "number of times to print")
    
    // Int64 flag
    var bigNumber = flag.Int64("bignum", 1000, "a big number")
    
    // Float64 flag
    var ratio = flag.Float64("ratio", 1.0, "a ratio value")
    
    // Bool flag
    var debug = flag.Bool("debug", false, "enable debug mode")
    
    // Duration flag
    var timeout = flag.Duration("timeout", 5*time.Second, "timeout duration")
    
    // Custom flag (using flag.Var)
    var customFlag customType
    flag.Var(&customFlag, "custom", "custom flag value")
    
    flag.Parse()
    
    fmt.Printf("Message: %s\n", *message)
    fmt.Printf("Count: %d\n", *count)
    fmt.Printf("Big Number: %d\n", *bigNumber)
    fmt.Printf("Ratio: %.2f\n", *ratio)
    fmt.Printf("Debug: %t\n", *debug)
    fmt.Printf("Timeout: %v\n", *timeout)
    fmt.Printf("Custom: %s\n", customFlag.value)
}

// Custom type for flag
type customType struct {
    value string
}

func (c *customType) String() string {
    return c.value
}

func (c *customType) Set(value string) error {
    c.value = "custom:" + value
    return nil
}

Flag Shortcuts

You can create shorter flag names using the same variable:

short-flags.go
package main

import (
    "flag"
    "fmt"
)

func main() {
    // Long and short versions of the same flag
    var output = flag.String("output", "output.txt", "output file")
    var o = flag.String("o", "output.txt", "output file (short)")
    
    flag.Parse()
    
    // Use the long version, but both will work
    fmt.Printf("Output file: %s\n", *output)
    fmt.Printf("Short output: %s\n", *o)
}

Subcommands

For programs with subcommands, you can handle them manually:

subcommands.go
package main

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

func main() {
    // Check if we have subcommands
    if len(os.Args) < 2 {
        fmt.Println("Usage: program <command> [arguments]")
        fmt.Println("Commands: create, delete, list")
        os.Exit(1)
    }
    
    subcommand := os.Args[1]
    
    switch subcommand {
    case "create":
        createCommand(os.Args[2:])
    case "delete":
        deleteCommand(os.Args[2:])
    case "list":
        listCommand(os.Args[2:])
    default:
        fmt.Printf("Unknown command: %s\n", subcommand)
        os.Exit(1)
    }
}

func createCommand(args []string) {
    fs := flag.NewFlagSet("create", flag.ExitOnError)
    name := fs.String("name", "", "name of item to create (required)")
    size := fs.Int("size", 100, "size of item")
    
    fs.Parse(args)
    
    if *name == "" {
        fs.Usage()
        os.Exit(1)
    }
    
    fmt.Printf("Creating item '%s' with size %d\n", *name, *size)
}

func deleteCommand(args []string) {
    fs := flag.NewFlagSet("delete", flag.ExitOnError)
    name := fs.String("name", "", "name of item to delete (required)")
    force := fs.Bool("force", false, "force deletion")
    
    fs.Parse(args)
    
    if *name == "" {
        fs.Usage()
        os.Exit(1)
    }
    
    if *force {
        fmt.Printf("Force deleting item '%s'\n", *name)
    } else {
        fmt.Printf("Deleting item '%s'\n", *name)
    }
}

func listCommand(args []string) {
    fs := flag.NewFlagSet("list", flag.ExitOnError)
    format := fs.String("format", "table", "output format (table, json, csv)")
    limit := fs.Int("limit", 10, "maximum number of items to list")
    
    fs.Parse(args)
    
    fmt.Printf("Listing items (format: %s, limit: %d)\n", *format, *limit)
}

Usage:

go run subcommands.go create -name "myitem" -size 200
go run subcommands.go delete -name "myitem" -force
go run subcommands.go list -format json -limit 50

Environment Variables

Sometimes you want to accept configuration from environment variables as well as flags:

env-flags.go
package main

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

func main() {
    // Define flags with default values
    var port = flag.Int("port", 8080, "server port")
    var host = flag.String("host", "localhost", "server host")
    var debug = flag.Bool("debug", false, "enable debug mode")
    
    // Override defaults with environment variables if set
    if envPort := os.Getenv("PORT"); envPort != "" {
        if p, err := parseInt(envPort); err == nil {
            *port = p
        }
    }
    
    if envHost := os.Getenv("HOST"); envHost != "" {
        *host = envHost
    }
    
    if envDebug := os.Getenv("DEBUG"); envDebug == "true" {
        *debug = true
    }
    
    flag.Parse()
    
    fmt.Printf("Server will run on %s:%d (debug: %t)\n", *host, *port, *debug)
}

// Simple integer parser (you'd use strconv.Atoi in real code)
func parseInt(s string) (int, error) {
    var result int
    for _, r := range s {
        if r < '0' || r > '9' {
            return 0, fmt.Errorf("invalid integer")
        }
        result = result*10 + int(r-'0')
    }
    return result, nil
}

Configuration File Support

For more complex configuration, you might want to read from a config file:

config-file.go
package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "os"
)

type Config struct {
    Port     int    `json:"port"`
    Host     string `json:"host"`
    Database struct {
        Host     string `json:"host"`
        Port     int    `json:"port"`
        Name     string `json:"name"`
        Username string `json:"username"`
        Password string `json:"password"`
    } `json:"database"`
}

func main() {
    var configFile = flag.String("config", "config.json", "configuration file")
    flag.Parse()
    
    config, err := loadConfig(*configFile)
    if err != nil {
        fmt.Printf("Error loading config: %v\n", err)
        os.Exit(1)
    }
    
    fmt.Printf("Server config: %s:%d\n", config.Host, config.Port)
    fmt.Printf("Database: %s:%d/%s\n", config.Database.Host, config.Database.Port, config.Database.Name)
}

func loadConfig(filename string) (*Config, error) {
    file, err := os.Open(filename)
    if err != nil {
        return nil, err
    }
    defer file.Close()
    
    var config Config
    decoder := json.NewDecoder(file)
    err = decoder.Decode(&config)
    if err != nil {
        return nil, err
    }
    
    return &config, nil
}

Example config.json:

{
  "port": 8080,
  "host": "0.0.0.0",
  "database": {
    "host": "localhost",
    "port": 5432,
    "name": "myapp",
    "username": "user",
    "password": "password"
  }
}

Best Practices

  1. Use meaningful flag names - Choose descriptive names for your flags
  2. Provide good help text - Use clear descriptions for each flag
  3. Handle required flags - Check for required flags and show usage if missing
  4. Use consistent naming - Follow conventions (kebab-case for flags, camelCase for variables)
  5. Support environment variables - Allow configuration via environment for containerized apps
  6. Validate input - Check flag values for validity
  7. Show usage on error - Use flag.Usage() to display help when flags are invalid

Advanced: Custom Flag Types

You can create custom flag types by implementing the flag.Value interface:

custom-flag.go
package main

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

// IntList represents a list of integers
type IntList []int

func (il *IntList) String() string {
    return fmt.Sprintf("%v", *il)
}

func (il *IntList) Set(value string) error {
    // Parse comma-separated values
    parts := strings.Split(value, ",")
    for _, part := range parts {
        part = strings.TrimSpace(part)
        if part == "" {
            continue
        }
        num, err := strconv.Atoi(part)
        if err != nil {
            return err
        }
        *il = append(*il, num)
    }
    return nil
}

func main() {
    var numbers IntList
    flag.Var(&numbers, "numbers", "comma-separated list of numbers")
    
    flag.Parse()
    
    fmt.Printf("Numbers: %v\n", numbers)
    
    // Calculate sum
    sum := 0
    for _, n := range numbers {
        sum += n
    }
    fmt.Printf("Sum: %d\n", sum)
}

Usage:

go run custom-flag.go -numbers "1,2,3,4,5"

External Resources:

Related Tutorials:

Last updated on