命令行工具:用 Go 构建优雅的 CLI 应用
Go 语言非常适合构建命令行工具。事实上,很多知名的 CLI 工具都是用 Go 写的:Docker、Kubernetes、Hugo、Terraform……
Go 的标准库提供了基础的命令行参数解析功能,而社区也涌现出了很多优秀的第三方库(如 cobra、urfave/cli),让构建复杂的 CLI 应用变得简单。
今天我们就来学习如何用 Go 构建专业的命令行工具。
基础:os.Args
最简单的方式是直接使用 os.Args:
package main
import (
"fmt"
"os"
)
func main() {
// os.Args[0] 是程序名
// os.Args[1:] 是命令行参数
fmt.Println("程序名:", os.Args[0])
fmt.Println("参数:", os.Args[1:])
if len(os.Args) < 2 {
fmt.Println("用法: myapp <name>")
os.Exit(1)
}
name := os.Args[1]
fmt.Printf("Hello, %s!\n", name)
}
运行:
$ go run main.go World
程序名: /tmp/go-build.../main
参数: [World]
Hello, World!
flag 包:标准库的参数解析
flag 包提供了基础的命令行参数解析:
package main
import (
"flag"
"fmt"
)
func main() {
// 定义参数
name := flag.String("name", "World", "要问候的人的名字")
age := flag.Int("age", 0, "年龄")
verbose := flag.Bool("verbose", false, "是否显示详细信息")
// 解析参数
flag.Parse()
// 使用参数
if *verbose {
fmt.Printf("详细信息: name=%s, age=%d\n", *name, *age)
}
fmt.Printf("Hello, %s!", *name)
if *age > 0 {
fmt.Printf(" You are %d years old.", *age)
}
fmt.Println()
// 获取非标志参数
args := flag.Args()
if len(args) > 0 {
fmt.Println("其他参数:", args)
}
}
运行:
$ go run main.go -name=Alice -age=30 -verbose
详细信息: name=Alice, age=30
Hello, Alice! You are 30 years old.
$ go run main.go --help
Usage of main:
-age int
年龄
-name string
要问候的人的名字 (default "World")
-verbose
是否显示详细信息
自定义标志类型
package main
import (
"flag"
"fmt"
"strings"
)
// 自定义类型:逗号分隔的字符串列表
type StringList []string
func (s *StringList) String() string {
return strings.Join(*s, ", ")
}
func (s *StringList) Set(value string) error {
*s = strings.Split(value, ",")
return nil
}
func main() {
var tags StringList
flag.Var(&tags, "tags", "标签列表(逗号分隔)")
flag.Parse()
fmt.Println("标签:", tags)
}
运行:
$ go run main.go -tags=go,cli,tutorial
标签: [go cli tutorial]
Cobra:专业的 CLI 框架
对于复杂的 CLI 应用,推荐使用 Cobra,它是 Go 社区最流行的 CLI 框架。
安装 Cobra
go get -u github.com/spf13/cobra/cobra
基础示例
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "MyApp 是一个示例 CLI 工具",
Long: `MyApp 是一个用 Go 和 Cobra 构建的示例 CLI 工具。`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Hello from MyApp!")
},
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
添加子命令
package main
import (
"fmt"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "MyApp 是一个示例 CLI 工具",
}
var greetCmd = &cobra.Command{
Use: "greet [name]",
Short: "问候某人",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
name := "World"
if len(args) > 0 {
name = args[0]
}
// 获取标志
times, _ := cmd.Flags().GetInt("times")
for i := 0; i < times; i++ {
fmt.Printf("Hello, %s!\n", name)
}
},
}
func init() {
// 添加标志
greetCmd.Flags().IntP("times", "t", 1, "问候次数")
// 注册子命令
rootCmd.AddCommand(greetCmd)
}
func main() {
rootCmd.Execute()
}
运行:
$ myapp greet Alice -t 3
Hello, Alice!
Hello, Alice!
Hello, Alice!
$ myapp greet --help
问候某人
Usage:
myapp greet [name] [flags]
Flags:
-h, --help help for greet
-t, --times int 问候次数 (default 1)
持久化标志
var verbose bool
func init() {
// 持久化标志在所有子命令中都可用
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "详细输出")
}
使用 cobra-cli 生成代码
Cobra 提供了一个代码生成工具:
# 安装
go install github.com/spf13/cobra-cli@latest
# 初始化项目
cobra-cli init myapp
# 添加命令
cobra-cli add serve
cobra-cli add config
实战:TODO 管理工具
让我们用 Cobra 构建一个完整的 TODO 管理工具:
package main
import (
"encoding/json"
"fmt"
"os"
"time"
"github.com/spf13/cobra"
)
type Todo struct {
ID int `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
CreatedAt time.Time `json:"created_at"`
}
type TodoList struct {
Todos []Todo `json:"todos"`
NextID int `json:"next_id"`
}
const dataFile = "todos.json"
func loadTodos() (*TodoList, error) {
list := &TodoList{NextID: 1}
data, err := os.ReadFile(dataFile)
if err != nil {
if os.IsNotExist(err) {
return list, nil
}
return nil, err
}
err = json.Unmarshal(data, list)
return list, err
}
func saveTodos(list *TodoList) error {
data, err := json.MarshalIndent(list, "", " ")
if err != nil {
return err
}
return os.WriteFile(dataFile, data, 0644)
}
var rootCmd = &cobra.Command{
Use: "todo",
Short: "一个简单的 TODO 管理工具",
}
var addCmd = &cobra.Command{
Use: "add [title]",
Short: "添加一个新的 TODO",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
list, err := loadTodos()
if err != nil {
fmt.Println("加载失败:", err)
return
}
todo := Todo{
ID: list.NextID,
Title: args[0],
Done: false,
CreatedAt: time.Now(),
}
list.Todos = append(list.Todos, todo)
list.NextID++
err = saveTodos(list)
if err != nil {
fmt.Println("保存失败:", err)
return
}
fmt.Printf("✓ 添加 TODO #%d: %s\n", todo.ID, todo.Title)
},
}
var listCmd = &cobra.Command{
Use: "list",
Short: "列出所有 TODO",
Run: func(cmd *cobra.Command, args []string) {
list, err := loadTodos()
if err != nil {
fmt.Println("加载失败:", err)
return
}
if len(list.Todos) == 0 {
fmt.Println("没有 TODO")
return
}
showDone, _ := cmd.Flags().GetBool("all")
for _, todo := range list.Todos {
if !showDone && todo.Done {
continue
}
status := "[ ]"
if todo.Done {
status = "[✓]"
}
fmt.Printf("%s #%d: %s\n", status, todo.ID, todo.Title)
}
},
}
var doneCmd = &cobra.Command{
Use: "done [id]",
Short: "标记 TODO 为完成",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
list, err := loadTodos()
if err != nil {
fmt.Println("加载失败:", err)
return
}
id := 0
fmt.Sscanf(args[0], "%d", &id)
for i, todo := range list.Todos {
if todo.ID == id {
list.Todos[i].Done = true
err = saveTodos(list)
if err != nil {
fmt.Println("保存失败:", err)
return
}
fmt.Printf("✓ TODO #%d 已完成\n", id)
return
}
}
fmt.Printf("✗ TODO #%d 不存在\n", id)
},
}
var deleteCmd = &cobra.Command{
Use: "delete [id]",
Short: "删除 TODO",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
list, err := loadTodos()
if err != nil {
fmt.Println("加载失败:", err)
return
}
id := 0
fmt.Sscanf(args[0], "%d", &id)
for i, todo := range list.Todos {
if todo.ID == id {
list.Todos = append(list.Todos[:i], list.Todos[i+1:]...)
err = saveTodos(list)
if err != nil {
fmt.Println("保存失败:", err)
return
}
fmt.Printf("✓ TODO #%d 已删除\n", id)
return
}
}
fmt.Printf("✗ TODO #%d 不存在\n", id)
},
}
func init() {
listCmd.Flags().BoolP("all", "a", false, "显示所有 TODO(包括已完成的)")
rootCmd.AddCommand(addCmd)
rootCmd.AddCommand(listCmd)
rootCmd.AddCommand(doneCmd)
rootCmd.AddCommand(deleteCmd)
}
func main() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
使用:
$ todo add "学习 Go 语言"
✓ 添加 TODO #1: 学习 Go 语言
$ todo add "写一个 CLI 工具"
✓ 添加 TODO #2: 写一个 CLI 工具
$ todo list
[ ] #1: 学习 Go 语言
[ ] #2: 写一个 CLI 工具
$ todo done 1
✓ TODO #1 已完成
$ todo list
[ ] #2: 写一个 CLI 工具
$ todo list -a
[✓] #1: 学习 Go 语言
[ ] #2: 写一个 CLI 工具
交互式输入
使用 bufio 读取用户输入:
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("请输入你的名字: ")
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
fmt.Printf("Hello, %s!\n", name)
// 确认操作
fmt.Print("确定要继续吗?(y/n): ")
confirm, _ := reader.ReadString('\n')
confirm = strings.TrimSpace(strings.ToLower(confirm))
if confirm == "y" || confirm == "yes" {
fmt.Println("继续执行...")
} else {
fmt.Println("已取消")
}
}
彩色输出
使用 fatih/color 库添加颜色:
package main
import (
"fmt"
"github.com/fatih/color"
)
func main() {
// 预定义颜色
color.Red("这是红色")
color.Green("这是绿色")
color.Blue("这是蓝色")
color.Yellow("这是黄色")
// 自定义样式
bold := color.New(color.Bold)
bold.Println("这是粗体")
underline := color.New(color.Underline)
underline.Println("这是下划线")
// 组合样式
success := color.New(color.FgGreen, color.Bold)
success.Println("✓ 操作成功")
error := color.New(color.FgRed, color.Bold)
error.Println("✗ 操作失败")
// 格式化输出
color.Cyan("用户 %s 的年龄是 %d", "张三", 25)
}
进度条
使用 schollz/progressbar/v3:
package main
import (
"fmt"
"time"
"github.com/schollz/progressbar/v3"
)
func main() {
bar := progressbar.Default(100, "处理中")
for i := 0; i < 100; i++ {
bar.Add(1)
time.Sleep(50 * time.Millisecond)
}
fmt.Println("\n完成!")
}
小结
今天我们学习了用 Go 构建命令行工具:
- 基础:
os.Args和flag包 - Cobra 框架:子命令、标志、参数验证
- 实战:TODO 管理工具
- 用户体验:交互式输入、彩色输出、进度条
Go 的编译型特性和标准库让构建 CLI 工具变得简单而高效。无论是简单的脚本还是复杂的工具,Go 都能胜任。
练习时间
- 实现一个文件统计工具,统计目录下的文件数量和大小
- 创建一个密码生成器,支持自定义长度和字符集
- 构建一个简单的书签管理工具
- 实现一个 HTTP 请求测试工具(类似 curl)
我们下篇见!
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。