cobra 是 go 語言的一個庫,可以用於編寫命令行工具。通常我們可以看到git pull
、docker container start
、apt install
等等這樣命令,都可以很容易用corba來實現,另外,go 語言是很容易編譯成一個二進制文件,本文將實現一個簡單的命令行工具。
具體寫一個例子, 設計一個命令叫blog
, 有四個子命令
blog new [post-name] :創建一篇新的blog
blog list :列出當前有哪些文章
blog delete [post-name]: 刪除某一篇文章
blog edit [post-name]:編輯某一篇文章
計划有以下幾個步驟
- 創建模塊
- 用cobra的命令行,創建命令行入口
- 用cobra的命令行,創建子命令
- 編寫功能邏輯
創建模塊
$ go mod init github.com/shalk/blog
go: creating new go.mod: module github.com/shalk/blog
創建命令行入口
說到命令行,可能會想到bash的getopt 或者 java 的jcommand,可以解析各種風格的命令行,但是通常這些命令行都有固定的寫法,這個寫法一般還記不住要找一個模板參考以下。cobra除了可以解析命令行之外,還提供了命令行,可以生成模板。先安裝這個命令行, 並且把庫的依賴加到go.mod里
$ go get -u github.com/spf13/cobra/cobra
cobra會安裝到$GOPATH\bin
目錄下,注意環境變量中把它加入PATH中
$ cobra init --pkg-name github.com/shalk/blog -a shalk -l mit
Your Cobra applicaton is ready at
D:\code\github.com\shalk\blog
目錄結構如下:
./cmd
./cmd/root.go
./go.mod
./go.sum
./LICENSE
./main.go
編譯一下
go build -o blog .
執行一下
$blog -h
A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.
命令行就創建好了,看上去一行代碼也不用寫,由於隨着了解的深入,后面需要調整生成的代碼,還是要了解一下cobra代碼的套路。
cobra代碼的套路
有三個概念,command、flag和args ,例如:
go get -u test.com/a/b
這里 get 就是commond(這里比較特殊), -u 就是flag, test.com/a/b 就是args
那么命令行就是有三部分構成,所以需要定義好這個
- 命令自身的一些基本信息,用command表示,具體對象是 cobra.Command
- 命令的一些標致或者選項,用flag表示,具體對象是 flag.FlagSet
- 最后的參數,用args表示,通常是[]string
還有一個概念是子命令,比如get就是go的子命令,這是一個樹狀結構的關系。
我可以使用go命令,也可以使用 go get命令
例如: root.go,定義了root命令,另外在init里面 定義了flag,如果它本身有具體執行,就填充Run字段。
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "blog",
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. For example:
Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(initConfig)
// Here you will define your flags and configuration settings.
// Cobra supports persistent flags, which, if defined here,
// will be global for your application.
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.blog.yaml)")
// Cobra also supports local flags, which will only run
// when this action is called directly.
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
如果需要子命令,就需要在init 里,給 rootCmd.AddCommand() 其他的command,其他的子命令通常也會單獨用一個文件編寫,並且有一個全局變量,讓rootCmd可以add它
創建子命令
D:\code\github.com\shalk\blog>cobra add new
new created at D:\code\github.com\shalk\blog
D:\code\github.com\shalk\blog>cobra add delete
delete created at D:\code\github.com\shalk\blog
D:\code\github.com\shalk\blog>cobra add list
list created at D:\code\github.com\shalk\blog
D:\code\github.com\shalk\blog>cobra add edit
edit created at D:\code\github.com\shalk\blog
cmd 目錄下增加了 new.go, delete.go,list.go,edit.go
增加功能代碼
new.go
var newCmd = &cobra.Command{
Use: "new",
Short: "create new post",
Long: `create new post `,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires a color argument")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
fileName := "posts/" + args[0]
err := os.Mkdir("posts", 644)
if err != nil {
log.Fatal(err)
}
_, err = os.Stat( fileName)
if os.IsNotExist(err) {
file, err := os.Create(fileName)
if err != nil {
log.Fatal(err)
}
log.Printf("create file %s", fileName)
defer file.Close()
} else {
}
},
}
list.go
var listCmd = &cobra.Command{
Use: "list",
Short: "list all blog in posts",
Long: `list all blog in posts `,
Run: func(cmd *cobra.Command, args []string) {
_, err := os.Stat("posts")
if os.IsNotExist(err) {
log.Fatal("posts dir is not exits")
}
dirs, err := ioutil.ReadDir("posts")
if err != nil {
log.Fatal("read posts dir fail")
}
fmt.Println("------------------")
for _, dir := range dirs {
fmt.Printf(" %s\n", dir.Name() )
}
fmt.Println("------------------")
fmt.Printf("total: %d blog\n", len(dirs))
},
}
delete.go
var deleteCmd = &cobra.Command{
Use: "delete",
Short: "delete a post",
Long: `delete a post`,
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("requires a color argument")
}
if strings.Contains(args[0],"/") || strings.Contains(args[0],"..") {
return errors.New("posts name should not contain / or .. ")
}
return nil
},
Run: func(cmd *cobra.Command, args []string) {
fileName := "./posts/" + args[0]
stat, err := os.Stat(fileName)
if os.IsNotExist(err) {
log.Fatalf("post %s is not exist", fileName)
}
if stat.IsDir() {
log.Fatalf("%s is dir ,can not be deleted", fileName)
}
err = os.Remove(fileName)
if err != nil {
log.Fatalf("delete %s fail, err %v", fileName, err)
} else {
log.Printf("delete post %s success", fileName)
}
},
}
edit.go 這個有一點麻煩,因為如果調用一個程序比如vim 打開文件,並且golang程序本身要退出,需要detach。暫時放一下(TODO)
編譯測試
我是window測試,linux 更簡單一點
PS D:\code\github.com\shalk\blog> go build -o blog.exe .
PS D:\code\github.com\shalk\blog> .\blog.exe list
------------------
------------------
total: 0 blog
PS D:\code\github.com\shalk\blog> .\blog.exe new blog1.md
2020/07/26 22:37:15 create file posts/blog1.md
PS D:\code\github.com\shalk\blog> .\blog.exe new blog2.md
2020/07/26 22:37:18 create file posts/blog2.md
PS D:\code\github.com\shalk\blog> .\blog.exe new blog3.md
2020/07/26 22:37:20 create file posts/blog3.md
PS D:\code\github.com\shalk\blog> .\blog list
------------------
blog1.md
blog2.md
blog3.md
------------------
total: 3 blog
PS D:\code\github.com\shalk\blog> .\blog delete blog1.md
2020/07/26 22:37:37 delete post ./posts/blog1.md success
PS D:\code\github.com\shalk\blog> .\blog list
------------------
blog2.md
blog3.md
------------------
total: 2 blog
PS D:\code\github.com\shalk\blog> ls .\posts\
目錄: D:\code\github.com\shalk\blog\posts
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 2020/7/26 22:37 0 blog2.md
-a---- 2020/7/26 22:37 0 blog3.md
PS D:\code\github.com\shalk\blog>
小結
cobra 是一個高效的命令行解析庫,利用cobra的腳手架,可以快速的實現一個命令行工具。如果需要更細致的控制,可以參考cobra的官方文檔。