VOOZH about

URL: https://dzone.com/articles/go-flags-beyond-the-basics

⇱ Go Flags: Beyond the Basics


Related

  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 Mar. 05, 25 · Tutorial
Likes
Comment
Save
4.5K 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

  • Clean Code: Concurrency Patterns, Context Management, and Goroutine Safety, Part 5
  • Building a Deterministic Event Correlation Engine in Go for High-Volume Alert Systems
  • Goose Migrations for Smooth Database Changes
  • Practical Generators in Go 1.23 for Database Pagination

Partner Resources

×

Comments

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

Let's be friends: