DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Goose Migrations for Smooth Database Changes
  • Practical Generators in Go 1.23 for Database Pagination
  • How To Create a Go CLI Tool That Converts Markdowns to PDF Files
  • CockroachDB TIL: Volume 11

Trending

  • Cookies Revisited: A Networking Solution for Third-Party Cookies
  • How to Configure and Customize the Go SDK for Azure Cosmos DB
  • Contextual AI Integration for Agile Product Teams
  • Optimizing Integration Workflows With Spark Structured Streaming and Cloud Services
  1. DZone
  2. Coding
  3. Languages
  4. Go Flags: Beyond the Basics

Go Flags: Beyond the Basics

Go's flag package helps you build command-line apps with argument parsing. Basic usage: flag.String(), flag.Int(), flag.Bool() for simple flags.

By 
Rez Moss user avatar
Rez Moss
·
Mar. 05, 25 · Tutorial
Likes (1)
Comment
Save
Tweet
Share
2.6K Views

Join the DZone community and get the full member experience.

Join For Free

The flag package is one of Go's most powerful standard library tools for building command-line applications. Understanding flags is essential whether you're creating a simple CLI tool or a complex application. Let's look into what makes this package so versatile.

Basic Flag Concepts

Let's start with a simple example that demonstrates the core concepts:

Go
 
package main

import (
    "flag"
    "fmt"
)

func main() {
    // Basic flag definitions
    name := flag.String("name", "guest", "your name")
    age := flag.Int("age", 0, "your age")
    isVerbose := flag.Bool("verbose", false, "enable verbose output")
    
    // Parse the flags
    flag.Parse()
    
    // Use the flags
    fmt.Printf("Hello %s (age: %d)!\n", *name, *age)
    if *isVerbose {
        fmt.Println("Verbose mode enabled")
    }
}


Advanced Usage Patterns

Custom Flag Types

Sometimes, you need flags that aren't just simple types. Here's how to create a custom flag type:

Go
 
type IntervalFlag struct {
    Duration time.Duration
}

func (i *IntervalFlag) String() string {
    return i.Duration.String()
}

func (i *IntervalFlag) Set(value string) error {
    duration, err := time.ParseDuration(value)
    if err != nil {
        return err
    }
    i.Duration = duration
    return nil
}

func main() {
    interval := IntervalFlag{Duration: time.Second}
    flag.Var(&interval, "interval", "interval duration (e.g., 10s, 1m)")
    flag.Parse()
}


Using Flag Sets

FlagSets are perfect for organizing flags in larger applications:

Go
 
package main

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

func main() {
    // Create flag sets for different commands
    serverCmd := flag.NewFlagSet("server", flag.ExitOnError)
    serverPort := serverCmd.Int("port", 8080, "server port")
    serverHost := serverCmd.String("host", "localhost", "server host")

    clientCmd := flag.NewFlagSet("client", flag.ExitOnError)
    clientTimeout := clientCmd.Duration("timeout", 30*time.Second, "client timeout")
    clientAddr := clientCmd.String("addr", "localhost:8080", "server address")

    if len(os.Args) < 2 {
        fmt.Println("expected 'server' or 'client' subcommand")
        os.Exit(1)
    }

    switch os.Args[1] {
    case "server":
        serverCmd.Parse(os.Args[2:])
        runServer(*serverHost, *serverPort)
    case "client":
        clientCmd.Parse(os.Args[2:])
        runClient(*clientAddr, *clientTimeout)
    default:
        fmt.Printf("unknown subcommand: %s\n", os.Args[1])
        os.Exit(1)
    }
}

func runServer(host string, port int) {
    fmt.Printf("Running server on %s:%d\n", host, port)
}

func runClient(addr string, timeout time.Duration) {
    fmt.Printf("Connecting to %s with timeout %v\n", addr, timeout)
}


Real-World Example: Configuration Manager

Here's a practical example of using flags to create a configuration management tool:

Go
 
package main

import (
    "flag"
    "fmt"
    "log"
    "os"
    "path/filepath"
)

type Config struct {
    ConfigPath string
    LogLevel   string
    Port       int
    Debug      bool
    DataDir    string
}

func main() {
    config := parseFlags()
    
    if err := run(config); err != nil {
        log.Fatal(err)
    }
}

func parseFlags() *Config {
    config := &Config{}
    
    // Define flags with environment variable fallbacks
    flag.StringVar(&config.ConfigPath, "config", 
        getEnvOrDefault("APP_CONFIG", "config.yaml"),
        "path to config file")
    
    flag.StringVar(&config.LogLevel, "log-level",
        getEnvOrDefault("APP_LOG_LEVEL", "info"),
        "logging level (debug, info, warn, error)")
    
    flag.IntVar(&config.Port, "port",
        getEnvIntOrDefault("APP_PORT", 8080),
        "server port number")
    
    flag.BoolVar(&config.Debug, "debug",
        getEnvBoolOrDefault("APP_DEBUG", false),
        "enable debug mode")
    
    flag.StringVar(&config.DataDir, "data-dir",
        getEnvOrDefault("APP_DATA_DIR", "./data"),
        "directory for data storage")
    
    // Custom usage message
    flag.Usage = func() {
        fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
        flag.PrintDefaults()
        fmt.Fprintf(os.Stderr, "\nEnvironment variables:\n")
        fmt.Fprintf(os.Stderr, "  APP_CONFIG     Path to config file\n")
        fmt.Fprintf(os.Stderr, "  APP_LOG_LEVEL  Logging level\n")
        fmt.Fprintf(os.Stderr, "  APP_PORT       Server port\n")
        fmt.Fprintf(os.Stderr, "  APP_DEBUG      Debug mode\n")
        fmt.Fprintf(os.Stderr, "  APP_DATA_DIR   Data directory\n")
    }
    
    flag.Parse()
    
    // Validate configurations
    if err := validateConfig(config); err != nil {
        log.Fatalf("Invalid configuration: %v", err)
    }
    
    return config
}

func validateConfig(config *Config) error {
    // Check if config file exists
    if _, err := os.Stat(config.ConfigPath); err != nil {
        return fmt.Errorf("config file not found: %s", config.ConfigPath)
    }
    
    // Validate log level
    switch config.LogLevel {
    case "debug", "info", "warn", "error":
        // Valid log levels
    default:
        return fmt.Errorf("invalid log level: %s", config.LogLevel)
    }
    
    // Validate port range
    if config.Port < 1 || config.Port > 65535 {
        return fmt.Errorf("port must be between 1 and 65535")
    }
    
    // Ensure data directory exists or create it
    if err := os.MkdirAll(config.DataDir, 0755); err != nil {
        return fmt.Errorf("failed to create data directory: %v", err)
    }
    
    return nil
}

// Helper functions for environment variables
func getEnvOrDefault(key, defaultValue string) string {
    if value, exists := os.LookupEnv(key); exists {
        return value
    }
    return defaultValue
}

func getEnvIntOrDefault(key string, defaultValue int) int {
    if value, exists := os.LookupEnv(key); exists {
        if parsed, err := strconv.Atoi(value); err == nil {
            return parsed
        }
    }
    return defaultValue
}

func getEnvBoolOrDefault(key string, defaultValue bool) bool {
    if value, exists := os.LookupEnv(key); exists {
        if parsed, err := strconv.ParseBool(value); err == nil {
            return parsed
        }
    }
    return defaultValue
}

func run(config *Config) error {
    log.Printf("Starting application with configuration:")
    log.Printf("  Config Path: %s", config.ConfigPath)
    log.Printf("  Log Level: %s", config.LogLevel)
    log.Printf("  Port: %d", config.Port)
    log.Printf("  Debug Mode: %v", config.Debug)
    log.Printf("  Data Directory: %s", config.DataDir)
    
    // Application logic here
    return nil
}


Best Practices and Tips

1. Default Values

Always provide sensible default values for your flags.

Go
 
port := flag.Int("port", 8080, "port number (default 8080)")


2. Clear Descriptions

Write clear, concise descriptions for each flag.

Go
 
flag.StringVar(&config, "config", "config.yaml", "path to configuration file")


3. Environment Variable Support

Consider supporting both flags and environment variables.

Go
 
flag.StringVar(&logLevel, "log-level", os.Getenv("LOG_LEVEL"), "logging level")


4. Validation

Always validate flag values after parsing.

Go
 
if *port < 1 || *port > 65535 {
    log.Fatal("Port must be between 1 and 65535")
}


5. Custom Usage

Provide clear usage information

Go
 
flag.Usage = func() {
    fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
    flag.PrintDefaults()
}


Advanced Patterns

Combining With cobra

While the flag package is powerful, you might want to combine it with more robust CLI frameworks like cobra:

Go
 
package main

import (
    "github.com/spf13/cobra"
    "github.com/spf13/viper"
)

func main() {
    var rootCmd = &cobra.Command{
        Use:   "myapp",
        Short: "My application description",
    }
    
    // Add flags that can also be set via environment variables
    rootCmd.PersistentFlags().String("config", "", "config file (default is $HOME/.myapp.yaml)")
    viper.BindPFlag("config", rootCmd.PersistentFlags().Lookup("config"))
    
    if err := rootCmd.Execute(); err != nil {
        log.Fatal(err)
    }
}


Common Pitfalls to Avoid

  1. Not calling flag.Parse(). Always call flag.Parse() after defining your flags.
  2. Ignoring errors. Handle flag parsing errors appropriately.
  3. Using global flags. Prefer using FlagSets for better organization in larger applications.
  4. Missing documentation. Always provide clear documentation for all flags.

Conclusion

The flag package is a powerful tool for building command-line applications in Go. You can create robust and user-friendly CLI applications by understanding its advanced features and following best practices. Remember to validate inputs, provide clear documentation, and consider combining with other packages for more complex applications.

Good luck with your CLI development!

Command-line interface Go (programming language) Data Types

Opinions expressed by DZone contributors are their own.

Related

  • Goose Migrations for Smooth Database Changes
  • Practical Generators in Go 1.23 for Database Pagination
  • How To Create a Go CLI Tool That Converts Markdowns to PDF Files
  • CockroachDB TIL: Volume 11

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: