[Go] command-line arguments
Updated:
개요
커맨드 라인 인자(Command Line Arguments)는 프로그램 실행 시 전달되는 파라미터입니다.
주요 특징:
- os.Args: 가장 기본적인 방법
- flag 패키지: 표준 라이브러리의 플래그 파싱
- cobra: 강력한 CLI 프레임워크 (kubectl, hugo 등에서 사용)
- urfave/cli: 간결한 CLI 도구
- 서브커맨드: git-style 명령어 구조
- 자동 도움말: -h, –help 지원
- 타입 안전: 문자열, 정수, 불린 등
os.Args 사용
1. 기본 사용법
package main
import (
"fmt"
"os"
)
func main() {
// os.Args[0]: 프로그램 이름
// os.Args[1:]: 인자들
fmt.Println("Program:", os.Args[0])
fmt.Println("Arguments:", os.Args[1:])
fmt.Println("Count:", len(os.Args)-1)
if len(os.Args) > 1 {
fmt.Println("First argument:", os.Args[1])
}
}
실행:
$ go run main.go hello world
Program: /tmp/go-build.../exe/main
Arguments: [hello world]
Count: 2
First argument: hello
2. 인자 파싱
func main() {
if len(os.Args) < 3 {
fmt.Println("Usage: program <name> <age>")
os.Exit(1)
}
name := os.Args[1]
age := os.Args[2]
fmt.Printf("Name: %s, Age: %s\n", name, age)
}
실행:
$ go run main.go John 30
Name: John, Age: 30
3. 타입 변환
import "strconv"
func main() {
if len(os.Args) < 3 {
fmt.Println("Usage: program <x> <y>")
os.Exit(1)
}
x, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Println("Error: x must be an integer")
os.Exit(1)
}
y, err := strconv.Atoi(os.Args[2])
if err != nil {
fmt.Println("Error: y must be an integer")
os.Exit(1)
}
fmt.Printf("%d + %d = %d\n", x, y, x+y)
}
4. 플래그와 위치 인자 혼합
func main() {
var verbose bool
args := []string{}
// 간단한 플래그 파싱
for _, arg := range os.Args[1:] {
if arg == "-v" || arg == "--verbose" {
verbose = true
} else {
args = append(args, arg)
}
}
if verbose {
fmt.Println("Verbose mode enabled")
}
fmt.Println("Arguments:", args)
}
flag 패키지
1. 기본 사용법
import "flag"
func main() {
// 플래그 정의
var name string
var age int
var verbose bool
flag.StringVar(&name, "name", "Guest", "Your name")
flag.IntVar(&age, "age", 0, "Your age")
flag.BoolVar(&verbose, "verbose", false, "Enable verbose output")
// 짧은 형식
flag.StringVar(&name, "n", "Guest", "Your name (shorthand)")
// 플래그 파싱
flag.Parse()
fmt.Printf("Name: %s, Age: %d, Verbose: %v\n", name, age, verbose)
// 남은 인자들
fmt.Println("Remaining args:", flag.Args())
}
실행:
$ go run main.go -name John -age 30 -verbose file1 file2
Name: John, Age: 30, Verbose: true
Remaining args: [file1 file2]
$ go run main.go -name=John -age=30
Name: John, Age: 30, Verbose: false
Remaining args: []
$ go run main.go --help
Usage of main:
-age int
Your age
-name string
Your name (default "Guest")
-verbose
Enable verbose output
2. 다양한 플래그 타입
func main() {
// 기본 타입
stringFlag := flag.String("string", "default", "a string")
intFlag := flag.Int("int", 0, "an int")
boolFlag := flag.Bool("bool", false, "a bool")
float64Flag := flag.Float64("float", 0.0, "a float64")
durationFlag := flag.Duration("duration", 0, "a duration")
flag.Parse()
fmt.Println("String:", *stringFlag)
fmt.Println("Int:", *intFlag)
fmt.Println("Bool:", *boolFlag)
fmt.Println("Float:", *float64Flag)
fmt.Println("Duration:", *durationFlag)
}
실행:
$ go run main.go -string=hello -int=42 -bool -float=3.14 -duration=5s
String: hello
Int: 42
Bool: true
Float: 3.14
Duration: 5s
3. 커스텀 플래그 타입
type StringSlice []string
func (s *StringSlice) String() string {
return strings.Join(*s, ",")
}
func (s *StringSlice) Set(value string) error {
*s = append(*s, value)
return nil
}
func main() {
var tags StringSlice
flag.Var(&tags, "tag", "Tag to add (can be repeated)")
flag.Parse()
fmt.Println("Tags:", tags)
}
실행:
$ go run main.go -tag=go -tag=cli -tag=tutorial
Tags: [go cli tutorial]
4. FlagSet (서브커맨드)
func main() {
// 서브커맨드별 FlagSet
addCmd := flag.NewFlagSet("add", flag.ExitOnError)
addName := addCmd.String("name", "", "Name to add")
listCmd := flag.NewFlagSet("list", flag.ExitOnError)
listVerbose := listCmd.Bool("verbose", false, "Verbose listing")
if len(os.Args) < 2 {
fmt.Println("expected 'add' or 'list' subcommands")
os.Exit(1)
}
switch os.Args[1] {
case "add":
addCmd.Parse(os.Args[2:])
fmt.Println("Adding:", *addName)
case "list":
listCmd.Parse(os.Args[2:])
fmt.Println("Listing, verbose:", *listVerbose)
default:
fmt.Println("expected 'add' or 'list' subcommands")
os.Exit(1)
}
}
실행:
$ go run main.go add -name=item1
Adding: item1
$ go run main.go list -verbose
Listing, verbose: true
5. 커스텀 Usage
func main() {
var name string
var age int
flag.StringVar(&name, "name", "", "Your name")
flag.IntVar(&age, "age", 0, "Your age")
// 커스텀 사용법 메시지
flag.Usage = func() {
fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS]\n", os.Args[0])
fmt.Fprintf(os.Stderr, "\nA simple greeting program\n\n")
fmt.Fprintf(os.Stderr, "OPTIONS:\n")
flag.PrintDefaults()
fmt.Fprintf(os.Stderr, "\nEXAMPLES:\n")
fmt.Fprintf(os.Stderr, " %s -name John -age 30\n", os.Args[0])
}
flag.Parse()
if name == "" {
flag.Usage()
os.Exit(1)
}
fmt.Printf("Hello, %s (%d years old)!\n", name, age)
}
Cobra 프레임워크
go get -u github.com/spf13/cobra@latest
1. 기본 구조
package main
import (
"fmt"
"github.com/spf13/cobra"
"os"
)
func main() {
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello from myapp!")
},
}
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
2. 플래그 추가
func main() {
var verbose bool
var output string
var count int
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "My application",
Run: func(cmd *cobra.Command, args []string) {
if verbose {
fmt.Println("Verbose mode enabled")
}
fmt.Printf("Output: %s, Count: %d\n", output, count)
fmt.Println("Args:", args)
},
}
// Persistent flags (모든 서브커맨드에 적용)
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
// Local flags (현재 커맨드만)
rootCmd.Flags().StringVarP(&output, "output", "o", "stdout", "output destination")
rootCmd.Flags().IntVarP(&count, "count", "c", 1, "repeat count")
rootCmd.Execute()
}
실행:
$ go run main.go -v -o file.txt -c 3 arg1 arg2
Verbose mode enabled
Output: file.txt, Count: 3
Args: [arg1 arg2]
3. 서브커맨드
func main() {
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "My application",
}
// 'add' 서브커맨드
var addCmd = &cobra.Command{
Use: "add [name]",
Short: "Add a new item",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Adding:", args[0])
},
}
// 'list' 서브커맨드
var listCmd = &cobra.Command{
Use: "list",
Short: "List all items",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Listing all items...")
},
}
// 'delete' 서브커맨드
var force bool
var deleteCmd = &cobra.Command{
Use: "delete [name]",
Short: "Delete an item",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
if force {
fmt.Println("Force deleting:", args[0])
} else {
fmt.Println("Deleting:", args[0])
}
},
}
deleteCmd.Flags().BoolVarP(&force, "force", "f", false, "force deletion")
rootCmd.AddCommand(addCmd, listCmd, deleteCmd)
rootCmd.Execute()
}
실행:
$ go run main.go add item1
Adding: item1
$ go run main.go list
Listing all items...
$ go run main.go delete -f item1
Force deleting: item1
4. PreRun/PostRun Hooks
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "My application",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("PersistentPreRun: Before any command")
},
PreRun: func(cmd *cobra.Command, args []string) {
fmt.Println("PreRun: Before root command")
},
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Run: Executing root command")
},
PostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("PostRun: After root command")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
fmt.Println("PersistentPostRun: After any command")
},
}
5. 인자 검증
var cmd = &cobra.Command{
Use: "greet [name]",
Short: "Greet someone",
Args: cobra.ExactArgs(1), // 정확히 1개 인자
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello,", args[0])
},
}
// 다른 검증 옵션들:
// cobra.NoArgs - 인자 없음
// cobra.MinimumNArgs(n) - 최소 n개
// cobra.MaximumNArgs(n) - 최대 n개
// cobra.RangeArgs(min, max) - min~max개
// cobra.ArbitraryArgs - 임의 개수
// 커스텀 검증
var customCmd = &cobra.Command{
Use: "custom",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return errors.New("requires at least one arg")
}
if !isValidName(args[0]) {
return fmt.Errorf("invalid name: %s", args[0])
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
// ...
},
}
6. 필수 플래그
var cmd = &cobra.Command{
Use: "login",
Run: func(cmd *cobra.Command, args []string) {
username, _ := cmd.Flags().GetString("username")
password, _ := cmd.Flags().GetString("password")
fmt.Printf("Logging in as %s\n", username)
},
}
cmd.Flags().String("username", "", "username (required)")
cmd.Flags().String("password", "", "password (required)")
// 필수로 지정
cmd.MarkFlagRequired("username")
cmd.MarkFlagRequired("password")
7. Cobra Generator
# Cobra CLI 설치
go install github.com/spf13/cobra-cli@latest
# 새 프로젝트 초기화
cobra-cli init
# 커맨드 추가
cobra-cli add serve
cobra-cli add config
cobra-cli add create -p configCmd
생성된 구조:
.
├── cmd/
│ ├── root.go
│ ├── serve.go
│ ├── config.go
│ └── create.go
└── main.go
urfave/cli
go get github.com/urfave/cli/v2
1. 기본 사용법
import (
"github.com/urfave/cli/v2"
)
func main() {
app := &cli.App{
Name: "myapp",
Usage: "A simple CLI application",
Action: func(c *cli.Context) error {
fmt.Println("Hello from myapp!")
return nil
},
}
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
2. 플래그
func main() {
app := &cli.App{
Name: "myapp",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
Aliases: []string{"n"},
Value: "Guest",
Usage: "your name",
},
&cli.IntFlag{
Name: "age",
Aliases: []string{"a"},
Value: 0,
Usage: "your age",
},
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "enable verbose output",
},
},
Action: func(c *cli.Context) error {
name := c.String("name")
age := c.Int("age")
verbose := c.Bool("verbose")
if verbose {
fmt.Println("Verbose mode enabled")
}
fmt.Printf("Hello, %s (%d years old)!\n", name, age)
return nil
},
}
app.Run(os.Args)
}
3. 서브커맨드
func main() {
app := &cli.App{
Name: "myapp",
Commands: []*cli.Command{
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a new item",
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
return cli.Exit("requires item name", 1)
}
fmt.Println("Adding:", c.Args().First())
return nil
},
},
{
Name: "list",
Aliases: []string{"l"},
Usage: "list all items",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "verbose",
Usage: "show detailed information",
},
},
Action: func(c *cli.Context) error {
verbose := c.Bool("verbose")
if verbose {
fmt.Println("Listing items (verbose)...")
} else {
fmt.Println("Listing items...")
}
return nil
},
},
{
Name: "delete",
Usage: "delete an item",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "force deletion without confirmation",
},
},
Action: func(c *cli.Context) error {
if c.NArg() < 1 {
return cli.Exit("requires item name", 1)
}
force := c.Bool("force")
item := c.Args().First()
if force {
fmt.Printf("Force deleting: %s\n", item)
} else {
fmt.Printf("Deleting: %s\n", item)
}
return nil
},
},
},
}
app.Run(os.Args)
}
4. Before/After Hooks
app := &cli.App{
Name: "myapp",
Before: func(c *cli.Context) error {
fmt.Println("Before: Setting up...")
return nil
},
After: func(c *cli.Context) error {
fmt.Println("After: Cleaning up...")
return nil
},
Action: func(c *cli.Context) error {
fmt.Println("Action: Running command...")
return nil
},
}
5. 환경 변수 통합
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Aliases: []string{"c"},
Value: "config.yaml",
Usage: "config file path",
EnvVars: []string{"MYAPP_CONFIG"}, // 환경 변수
},
&cli.IntFlag{
Name: "port",
Value: 8080,
Usage: "server port",
EnvVars: []string{"MYAPP_PORT", "PORT"},
},
},
Action: func(c *cli.Context) error {
config := c.String("config")
port := c.Int("port")
fmt.Printf("Config: %s, Port: %d\n", config, port)
return nil
},
}
실행:
$ export MYAPP_PORT=9000
$ go run main.go
Config: config.yaml, Port: 9000
$ go run main.go --port 3000
Config: config.yaml, Port: 3000
실전 예제
1. 파일 처리 도구
package main
import (
"fmt"
"io"
"os"
"github.com/spf13/cobra"
)
func main() {
var inputFile string
var outputFile string
var verbose bool
var rootCmd = &cobra.Command{
Use: "fileutil",
Short: "File utility tool",
}
var copyCmd = &cobra.Command{
Use: "copy",
Short: "Copy file",
Run: func(cmd *cobra.Command, args []string) {
if verbose {
fmt.Printf("Copying %s to %s\n", inputFile, outputFile)
}
if err := copyFile(inputFile, outputFile); err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
fmt.Println("File copied successfully")
},
}
copyCmd.Flags().StringVarP(&inputFile, "input", "i", "", "input file (required)")
copyCmd.Flags().StringVarP(&outputFile, "output", "o", "", "output file (required)")
copyCmd.MarkFlagRequired("input")
copyCmd.MarkFlagRequired("output")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose output")
rootCmd.AddCommand(copyCmd)
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, sourceFile)
return err
}
2. HTTP 서버 CLI
func main() {
var port int
var host string
var debug bool
var rootCmd = &cobra.Command{
Use: "server",
Short: "HTTP server",
}
var serveCmd = &cobra.Command{
Use: "serve [directory]",
Short: "Start HTTP server",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
dir := "."
if len(args) > 0 {
dir = args[0]
}
addr := fmt.Sprintf("%s:%d", host, port)
if debug {
fmt.Printf("Debug mode enabled\n")
}
fmt.Printf("Serving %s on http://%s\n", dir, addr)
http.Handle("/", http.FileServer(http.Dir(dir)))
if err := http.ListenAndServe(addr, nil); err != nil {
fmt.Println("Error:", err)
os.Exit(1)
}
},
}
serveCmd.Flags().IntVarP(&port, "port", "p", 8080, "server port")
serveCmd.Flags().StringVarP(&host, "host", "H", "0.0.0.0", "server host")
rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "debug mode")
rootCmd.AddCommand(serveCmd)
rootCmd.Execute()
}
실행:
$ go run main.go serve -p 3000 ./public
Serving ./public on http://0.0.0.0:3000
3. 데이터베이스 마이그레이션 도구
func main() {
var dbURL string
var migrationsPath string
app := &cli.App{
Name: "migrate",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "database-url",
Aliases: []string{"d"},
Required: true,
EnvVars: []string{"DATABASE_URL"},
Usage: "database connection URL",
},
&cli.StringFlag{
Name: "migrations",
Aliases: []string{"m"},
Value: "./migrations",
Usage: "migrations directory",
},
},
Commands: []*cli.Command{
{
Name: "up",
Usage: "run all up migrations",
Action: func(c *cli.Context) error {
dbURL = c.String("database-url")
migrationsPath = c.String("migrations")
fmt.Printf("Running migrations from %s\n", migrationsPath)
fmt.Printf("Database: %s\n", maskDBURL(dbURL))
// 실제 마이그레이션 로직
return runMigrations(dbURL, migrationsPath, "up")
},
},
{
Name: "down",
Usage: "rollback one migration",
Action: func(c *cli.Context) error {
dbURL = c.String("database-url")
migrationsPath = c.String("migrations")
fmt.Println("Rolling back last migration...")
return runMigrations(dbURL, migrationsPath, "down")
},
},
{
Name: "status",
Usage: "show migration status",
Action: func(c *cli.Context) error {
dbURL = c.String("database-url")
fmt.Println("Checking migration status...")
return checkMigrationStatus(dbURL)
},
},
},
}
app.Run(os.Args)
}
func maskDBURL(url string) string {
// postgres://user:password@host/db -> postgres://user:***@host/db
re := regexp.MustCompile(`://([^:]+):([^@]+)@`)
return re.ReplaceAllString(url, "://$1:***@")
}
4. Git-style CLI
func main() {
var rootCmd = &cobra.Command{
Use: "git",
Short: "Git-like version control",
}
// git init
var initCmd = &cobra.Command{
Use: "init [directory]",
Short: "Initialize a repository",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
dir := "."
if len(args) > 0 {
dir = args[0]
}
fmt.Printf("Initialized repository in %s\n", dir)
},
}
// git add
var addCmd = &cobra.Command{
Use: "add [files...]",
Short: "Add files to staging",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
for _, file := range args {
fmt.Printf("Adding %s\n", file)
}
},
}
// git commit
var message string
var commitCmd = &cobra.Command{
Use: "commit",
Short: "Commit changes",
Run: func(cmd *cobra.Command, args []string) {
if message == "" {
fmt.Println("Error: commit message required")
os.Exit(1)
}
fmt.Printf("Committed: %s\n", message)
},
}
commitCmd.Flags().StringVarP(&message, "message", "m", "", "commit message")
commitCmd.MarkFlagRequired("message")
// git log
var limit int
var logCmd = &cobra.Command{
Use: "log",
Short: "Show commit logs",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Showing last %d commits\n", limit)
},
}
logCmd.Flags().IntVarP(&limit, "limit", "n", 10, "number of commits")
rootCmd.AddCommand(initCmd, addCmd, commitCmd, logCmd)
rootCmd.Execute()
}
실행:
$ go run main.go init myrepo
Initialized repository in myrepo
$ go run main.go add file1.go file2.go
Adding file1.go
Adding file2.go
$ go run main.go commit -m "Initial commit"
Committed: Initial commit
$ go run main.go log -n 5
Showing last 5 commits
설정 우선순위
1. 우선순위 체계
type Config struct {
Port int
Host string
Verbose bool
}
func LoadConfig(cmd *cobra.Command) *Config {
cfg := &Config{}
// 1. 기본값
cfg.Port = 8080
cfg.Host = "localhost"
cfg.Verbose = false
// 2. 설정 파일
if configFile := viper.GetString("config"); configFile != "" {
viper.SetConfigFile(configFile)
if err := viper.ReadInConfig(); err == nil {
cfg.Port = viper.GetInt("port")
cfg.Host = viper.GetString("host")
cfg.Verbose = viper.GetBool("verbose")
}
}
// 3. 환경 변수
if port := os.Getenv("APP_PORT"); port != "" {
cfg.Port, _ = strconv.Atoi(port)
}
if host := os.Getenv("APP_HOST"); host != "" {
cfg.Host = host
}
// 4. 커맨드 라인 플래그 (최우선)
if cmd.Flags().Changed("port") {
cfg.Port, _ = cmd.Flags().GetInt("port")
}
if cmd.Flags().Changed("host") {
cfg.Host, _ = cmd.Flags().GetString("host")
}
if cmd.Flags().Changed("verbose") {
cfg.Verbose, _ = cmd.Flags().GetBool("verbose")
}
return cfg
}
우선순위:
- 커맨드 라인 플래그 (최우선)
- 환경 변수
- 설정 파일
- 기본값
2. Viper 통합
import (
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func main() {
var cfgFile string
var rootCmd = &cobra.Command{
Use: "myapp",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
// Viper 초기화
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
viper.AddConfigPath(".")
viper.SetConfigName("config")
}
viper.AutomaticEnv()
viper.SetEnvPrefix("MYAPP")
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
// 플래그를 Viper에 바인딩
viper.BindPFlags(cmd.Flags())
},
Run: func(cmd *cobra.Command, args []string) {
port := viper.GetInt("port")
host := viper.GetString("host")
fmt.Printf("Host: %s, Port: %d\n", host, port)
},
}
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
rootCmd.Flags().Int("port", 8080, "server port")
rootCmd.Flags().String("host", "localhost", "server host")
rootCmd.Execute()
}
일반적인 실수
1. 인자 검증 누락
// ❌ 나쁜 예
func main() {
name := os.Args[1] // 패닉 가능!
fmt.Println(name)
}
// ✅ 좋은 예
func main() {
if len(os.Args) < 2 {
fmt.Println("Usage: program <name>")
os.Exit(1)
}
name := os.Args[1]
fmt.Println(name)
}
2. 타입 변환 에러 무시
// ❌ 나쁜 예
age, _ := strconv.Atoi(os.Args[1])
// ✅ 좋은 예
age, err := strconv.Atoi(os.Args[1])
if err != nil {
fmt.Println("Error: age must be an integer")
os.Exit(1)
}
3. 플래그 파싱 전 접근
// ❌ 나쁜 예
var name string
flag.StringVar(&name, "name", "Guest", "name")
fmt.Println(name) // 항상 "Guest"
flag.Parse()
// ✅ 좋은 예
var name string
flag.StringVar(&name, "name", "Guest", "name")
flag.Parse() // 먼저 파싱
fmt.Println(name)
4. 도움말 메시지 부족
// ❌ 나쁜 예
flag.String("config", "", "")
// ✅ 좋은 예
flag.String("config", "config.yaml", "path to configuration file")
5. 서브커맨드 없는 복잡한 도구
// ❌ 나쁜 예: 플래그만으로 모든 기능
// program --add --delete --list
// ✅ 좋은 예: 서브커맨드 사용
// program add
// program delete
// program list
6. 에러 처리 누락
// ❌ 나쁜 예
app.Run(os.Args)
// ✅ 좋은 예
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
7. 불명확한 플래그 이름
// ❌ 나쁜 예
flag.Bool("v", false, "")
flag.Bool("d", false, "")
// ✅ 좋은 예
flag.BoolVar(&verbose, "verbose", false, "enable verbose output")
flag.BoolVar(&verbose, "v", false, "enable verbose output (shorthand)")
flag.BoolVar(&debug, "debug", false, "enable debug mode")
베스트 프랙티스
1. 명확한 명명
// ✅ 명확한 커맨드/플래그 이름
cobra.Command{
Use: "user create",
Short: "Create a new user",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "username",
Usage: "username for the new user",
},
&cli.StringFlag{
Name: "email",
Usage: "email address",
},
},
}
2. 버전 정보 제공
var version = "1.0.0"
var buildDate = "2024-01-01"
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "My application",
Version: version,
}
var versionCmd = &cobra.Command{
Use: "version",
Short: "Print version information",
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("Version: %s\n", version)
fmt.Printf("Build Date: %s\n", buildDate)
},
}
3. 풍부한 도움말
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "A brief description",
Long: `A longer description that explains what the application does,
how to use it, and provides examples.
Examples:
myapp serve --port 8080
myapp migrate up
myapp user create --username john`,
}
4. 환경 변수 통합
app := &cli.App{
Flags: []cli.Flag{
&cli.StringFlag{
Name: "api-key",
Usage: "API key for authentication",
EnvVars: []string{"API_KEY", "MYAPP_API_KEY"},
},
},
}
5. 설정 파일 지원
var rootCmd = &cobra.Command{
PersistentPreRun: func(cmd *cobra.Command, args []string) {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
viper.ReadInConfig()
}
},
}
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file")
6. 진행 상황 표시
import "github.com/cheggaaa/pb/v3"
func processFiles(files []string) {
bar := pb.StartNew(len(files))
for _, file := range files {
processFile(file)
bar.Increment()
}
bar.Finish()
}
7. 컬러 출력
import "github.com/fatih/color"
func main() {
color.Green("Success: Operation completed")
color.Red("Error: Something went wrong")
color.Yellow("Warning: This is deprecated")
// 조건부 컬러 (터미널 감지)
if isTerminal() {
color.Blue("Info: Processing...")
} else {
fmt.Println("Info: Processing...")
}
}
8. 대화형 프롬프트
import "github.com/manifoldco/promptui"
func confirmDelete() bool {
prompt := promptui.Prompt{
Label: "Are you sure you want to delete",
IsConfirm: true,
}
_, err := prompt.Run()
return err == nil
}
func selectOption() string {
prompt := promptui.Select{
Label: "Select environment",
Items: []string{"development", "staging", "production"},
}
_, result, _ := prompt.Run()
return result
}
정리
- os.Args: 가장 기본적인 방법, 간단한 도구에 적합
- flag 패키지: 표준 라이브러리, 플래그 파싱, POSIX 스타일
- cobra: 강력한 CLI 프레임워크, 서브커맨드, kubectl/hugo 스타일
- urfave/cli: 간결한 API, 빠른 개발
- 서브커맨드: git-style 명령어 구조
- 플래그 타입: string, int, bool, duration 등
- 환경 변수: 플래그와 통합 가능
- 우선순위: CLI > 환경 변수 > 설정 파일 > 기본값
- 실수: 검증 누락, 에러 무시, 파싱 순서, 도움말 부족
- 베스트: 명확한 명명, 버전 정보, 도움말, 환경 변수 통합, 진행 표시
- 원칙: 사용자 친화적, 명확한 에러 메시지, UNIX 철학