cli库使用文档和实例

简介

cli是一个用于构建命令行程序的库。非常简洁,所有的初始化操作就是创建一个cli.App结构的对象。通过为对象的字段赋值来添加相应的功能。

快速使用

cli需要搭配 Go Modules 使用。创建目录并初始化:

$ mkdir cli && cd cli
$ go mod init github.com/darjun/go-daily-lib/cli

安装cli库,有v1v2两个版本。如果没有特殊需求,一般安装v2版本:

$ go get -u github.com/urfave/cli/v2

使用:

package main

import (
  "fmt"
  "log"
  "os"

  "github.com/urfave/cli/v2"
)

func main() {
  app := &cli.App{
    Name:  "hello",
    Usage: "hello world example",
    Action: func(c *cli.Context) error {
      fmt.Println("hello world")
      return nil
    },
  }

  err := app.Run(os.Args)
  if err != nil {
    log.Fatal(err)
  }
}

使用非常简单,理论上创建一个cli.App结构的对象,然后调用其Run()方法,传入命令行的参数即可。一个空白的cli应用程序如下:

func main() {
  (&cli.App{}).Run(os.Args)
}

但是这个空白程序没有什么用处。我们的hello world程序,设置了Name/Usage/ActionNameUsage都显示在帮助中,Action是调用该命令行程序时实际执行的函数,需要的信息可以从参数cli.Context获取。

编译、运行(环境:Win10 + Git Bash):

$ go build -o hello
$ ./hello
hello world

除了这些,cli为我们额外生成了帮助信息:

$ ./hello --help
NAME:
   hello - hello world example

USAGE:
   hello [global options] command [command options] [arguments...]

COMMANDS:
   help, h  Shows a list of commands or help for one command

GLOBAL OPTIONS:
   --help, -h  show help (default: false)

如果是需要看各种参数及具体选型使用,可移步:https://segmentfault.com/a/1190000023009961

接下来是一个小的实例: 使用cli分模块的配置各个小功能及实现:

先看下目录结构:

longyu@longyu:/xx/c/xxx/xx/GolandProjects/ADBTools$ tree -a
.
├── .idea									#goland的目录 不用管
│   ├── .gitignore
│   ├── ADBTools.iml
│   ├── MarsCodeWorkspaceAppSettings.xml
│   ├── modules.xml
│   └── workspace.xml
├── ADBTools.exe							#最终生成的运行工具
├── cmd										#每一个具体的功能选型
│   ├── RunCreateUser.go
│   └── runRuokouling.go
├── go.mod
├── go.sum
├── main.go									#主入口
├── rksOUTPUT								#结果输出
│   ├── 创建用户SQL_2025-07-07.xlsx
│   └── 数据库弱口令筛查_2025-07.xlsx
└── utils									#具体实现其功能的实际操作代码
    ├── CreateUserSQLAction.go
    └── RuoKouLingAction.go

main.go

package main

import (
	"ADBTools/cmd"
	"github.com/urfave/cli/v2"
	"log"
	"os"
)

func main() {
	app := &cli.App{
		EnableBashCompletion: true,
		Name:                 "ADBTools",
		Usage:                "用于使用便捷的小工具",
        
		Description: `ADBTools 是一个多功能命令行工具集合,目前支持:
                        - 弱口令筛查
                        - 创建用户sql输出
                        后续将持续扩展更多实用功能。
`,
		Version: "v0.0.1",
		Commands: []*cli.Command{
			cmd.RunRuokouling(),
			cmd.RunCreateUser(),
		},
	}
	err := app.Run(os.Args)
	if err != nil {
		log.Fatal(err)
	}
}

这里配置了整体命令的主框架,简介一下这个工作的用作,功能,作者等,就是个简单的介绍.

Commands处将写出具体实现的功能接口,如cmd.RunCreateUser() 即是创建用户sql输出的功能接口.

接下来就以cmd.RunCreateUser() 这个功能接口展示:

RunCreateUser.go

package cmd

import (
	"ADBTools/utils"
	"github.com/urfave/cli/v2"
)

func RunCreateUser() *cli.Command {
	return &cli.Command{
		Name:    "CreateUserSQL",
		Aliases: []string{"cus"},
		Usage:   "生成用户创建SQL",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:     "account",
				Aliases:  []string{"c"},
				Usage:    "账户信息(Excel_xxxx.xls)",
				Required: true,
			},
		},
		Action: utils.CreateUserSQLAction,
	}

}

这里去配置 "生成用户创建SQL" 这个功能具体的介绍(Usage),和需要传参的具体配置(Flags),最后在Action中 编写具体是实现方案

CreateUserSQLAction.go

package utils

import (
	"fmt"
	"github.com/urfave/cli/v2"
	"github.com/xuri/excelize/v2"
	"log"
	"os"
	"strings"
	"time"
)

// 清洗 header 函数
func cleanHeader(s string) string {
	s = strings.TrimSpace(s)
	return strings.Join(strings.Fields(s), " ")
}
func excelToMap(filePath string) ([]map[string]interface{}, []string, error) {
	// 打开 Excel 文件
	f, err := excelize.OpenFile(filePath)
	if err != nil {
		fmt.Println("打开文件失败:", err)
		return nil, nil, err
	}

	// 获取第一个工作表名称
	sheetName := f.GetSheetName(f.GetActiveSheetIndex())

	// 获取所有行数据
	rows, err := f.GetRows(sheetName)
	if err != nil {
		fmt.Println("读取行失败:", err)
		return nil, nil, err
	}

	// 1️⃣ 获取并清洗表头
	rawHeaders := rows[0]
	headers := make([]string, len(rawHeaders))
	for i, h := range rawHeaders {
		headers[i] = cleanHeader(h)
	}
	var data []map[string]interface{}

	// 遍历其余行
	for i := 1; i < len(rows); i++ {
		row := rows[i]
		rowMap := make(map[string]interface{})

		for j, header := range headers {
			if j < len(row) {
				rowMap[header] = row[j]
			} else {
				rowMap[header] = nil
			}
		}

		data = append(data, rowMap)
	}
	return data, headers, nil
}

func CreateUserSQLAction(c *cli.Context) error {
	//ctx := c.Context

	userlistFile := c.String("account")
	if userlistFile == "" {
		log.Fatal("必须提供 --account 文件路径")
	}

	fmt.Println("任务开始,按 Ctrl+C 取消...")

	usersList, _, err := excelToMap(userlistFile)
	if err != nil {
		log.Fatalf("无法读取用户表文件: %v", err)
	}

	var outputMap []RowData
	var createSQL, grantSQL string
	for index, user := range usersList {
		fmt.Println("第", index+1, "行:", user)
		if val, ok := user["数据库类型"].(string); ok {
			fmt.Println("数据库类型:", val)
			if strings.Contains(strings.ToLower(val), "oracle") || strings.Contains(strings.ToLower(val), "ora") {
				fmt.Println("匹配到Oracle数据库")
				// 匹配 Oracle, 编写oracle版的创建用户SQL
				createSQL = "CREATE USER " + user["登录帐号"].(string) + " IDENTIFIED BY " + user["登录密码"].(string) + ";"
				// 赋权sql, 连接,增删改查
				grantSQL = "GRANT CONNECT, CREATE ANY TABLE, UPDATE ANY TABLE , DELETE ANY TABLE, SELECT ANY TABLE TO " + user["登录帐号"].(string) + ";"
			} else if strings.Contains(strings.ToLower(val), "pg") || strings.Contains(strings.ToLower(val), "postgresql") || strings.Contains(strings.ToLower(val), "panwei") {
				createSQL = "CREATE USER " + user["登录帐号"].(string) + " WITH PASSWORD '" + user["登录密码"].(string) + "';"
				grantSQL = "GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA xxxxxx TO " + user["登录帐号"].(string) + ";"
			} else {
				fmt.Println("匹配到非oracle的数据库")
				// 以mysql风格创建用户
				createSQL = "CREATE USER '" + user["登录帐号"].(string) + "'@'%' IDENTIFIED BY '" + user["登录密码"].(string) + "';"
				grantSQL = "GRANT SELECT, INSERT, UPDATE, DELETE ON *.* TO '" + user["登录帐号"].(string) + "'@'%';"
			}
		}
		beizhustr := ""
		if bz, ok := user["备注"].(string); !ok {
			beizhustr = ""
		} else {
			beizhustr = bz
		}
		outputMap = append(outputMap, RowData{
			ziyuanming: user["资源名"].(string),
			user:       user["登录帐号"].(string),
			shili:      user["数据库实例"].(string),
			ip:         user["数据库IP"].(string),
			leixing:    user["数据库类型"].(string),
			beizhu:     beizhustr,
			createSQL:  createSQL,
			grantSQL:   grantSQL,
		})
	}
	fmt.Println("============================================================")
	//打开新文件 把创建用户SQL写入一个新sheet里
	newFile := excelize.NewFile()
	newFile.NewSheet("创建用户SQL")
	for index, rows := range outputMap {
		fmt.Println("资源名:", rows.ziyuanming, "用户名:", rows.user, "数据库实例:", rows.shili, "数据库IP:", rows.ip, "数据库类型:", rows.leixing)
		headers := []interface{}{"创建用户SQL", "赋权SQL", "资源名", "用户名", "数据库实例", "数据库IP", "数据库类型"}
		for colNum, header := range headers {
			cell, _ := excelize.CoordinatesToCellName(colNum+1, 1)
			newFile.SetCellValue("创建用户SQL", cell, header)
		}
		rowIdx := index + 2
		newFile.SetCellValue("创建用户SQL", "A"+fmt.Sprint(rowIdx), rows.createSQL)
		newFile.SetCellValue("创建用户SQL", "B"+fmt.Sprint(rowIdx), rows.grantSQL)
		newFile.SetCellValue("创建用户SQL", "C"+fmt.Sprint(rowIdx), rows.ziyuanming)
		newFile.SetCellValue("创建用户SQL", "D"+fmt.Sprint(rowIdx), rows.user)
		newFile.SetCellValue("创建用户SQL", "E"+fmt.Sprint(rowIdx), rows.shili)
		newFile.SetCellValue("创建用户SQL", "F"+fmt.Sprint(rowIdx), rows.ip)
		newFile.SetCellValue("创建用户SQL", "G"+fmt.Sprint(rowIdx), rows.leixing)
		newFile.SetCellValue("创建用户SQL", "H"+fmt.Sprint(rowIdx), rows.beizhu)
	}
	//删除默认sheet
	newFile.DeleteSheet("Sheet1")
	outputDir := "rksOUTPUT"
	if err := os.MkdirAll(outputDir, os.ModePerm); err != nil {
		log.Fatalf("创建输出目录失败: %v", err)
	}
	loc, _ := time.LoadLocation("Asia/Shanghai")
	currentTime := time.Now().In(loc).Format("2006-01-01")
	outputFileName := fmt.Sprintf("%s/创建用户SQL_%s.xlsx", outputDir, currentTime)
	if err := newFile.SaveAs(outputFileName); err != nil {
		log.Fatalf("保存 Excel 文件失败: %v", err)
	}

	fmt.Printf("✅ %s 文件已成功生成!", outputFileName)

	return nil
}

在以上方法中,就没有cli那么标准化的要求了,只需要从CreateUserSQLAction(c *cli.Context) 中 *cli.Context将上面传参接过来,通过userlistFile := c.String("account")获得具体参数,即可开始正常的功能书写.

由代码得知,我们通过一系列方法将excel中标题进行清洗整理, 并按照具体的数据库类型输出具体的create user语句.