はじめに
command line cliなscriptを作りたいとき、
たいていどの言語でも引数を扱うライブラリがありますよね。
golangではとても便利なurfave/cliというパッケージがあります。
(以前は github.com/codegangsta/cli というリポジトリでした)
この使い方を簡単にメモ
アジェンダ
- getting start的な
- Arguments とか
- Flags 使ってみる
- Subcommands でいろいろ
その前に
この記事に使用したソースです。
https://github.com/tweeeety/go-command-line-sample/tree/master/src/script
1. getting start的な
install
go getするだけです。
glide使ってればglide installするだけですね。
$ go get github.com/urfave/cli
使ってみる
前提
これ以降の記述は、すべて以下のようなdir構成になっているものとして進めます。
今回の記事ようにgo-command-line-sampleというプロジェクト ディレクトリを作ってます。
# 作成したgo-command-line-sample
$ pwd
パス/go-command-line-sample
$ tree -L 3 ../go-command-line-sample
../go-command-line-sample
└── src
└── script # この配下にscriptごとにdirを切る
├── glide.lock
├── glide.yaml
└── vendor
簡単に試す
まずはprintするだけ、help出すだけ。
公式と同じですね
src/script/cli_10/main.go
package main import ( "10t" "os" "github.com/urfave/cli" ) func main() { app := cli.NewApp() app.Name = "boom" app.Usage = "make an explosive entrance" app.Action = func(c *cli.Context) error { fmt.Println("boom! I say!") return nil } app.Run(os.Args) }
実行
# 叩いてみる
$ go run src/script/cli_10/main.go
boom! I say!
# helpってみる
$ go run src/script/cli_10/main.go help
NAME:
boom - make an explosive entrance
USAGE:
main [global options] command [command options] [arguments...]
VERSION:
0.0.0
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help
--version, -v print the version
go install
おもむろにgo installしてみます。
# scriptがいる場所まで移動
$ cd src/script/cli_10
# install
$ go instal
# プロジェクト直下にもどる
$ cd パス/go-command-line-sample
# binにcli_10が出来てる
$ tree -L 3
.
├── bin
│ └── cli_10
├── pkg
│ └── darwin_amd64
│ └── script
└── src
└── script
├── cli_10
├── glide.lock
├── glide.yaml
└── vendor
# たたいてみる
$ ./bin/cli_10
boom! I say!
ここまで動かすのもめっちゃ簡単ですね!
2. Arguments とか
app := cli.NewApp() した後の基本的な使い方です。
src/script/cli_20/main.go
package main import ( "fmt" "os" "github.com/urfave/cli" ) func main() { app := cli.NewApp() app.Name = "cli_20" app.Usage = "cli_20 sample" app.Version = "1.2.1" // before app.Before = func(c *cli.Context) error { fmt.Println("-- Before --") return nil } // action app.Action = func(c *cli.Context) error { fmt.Println("-- Action --") fmt.Printf("c.NArg() : %+v\n", c.NArg()) fmt.Printf("c.Args() : %+v\n", c.Args()) fmt.Printf("c.Args().Get(0) : %+v\n", c.Args().Get(0)) fmt.Printf("c.Args()[0] : %+v\n", c.Args()[0]) fmt.Printf("c.FlagNames : %+v\n", c.FlagNames()) // Help表示 //cli.ShowAppHelp(c) // version表示 cli.ShowVersion(c) return nil } // after app.After = func(c *cli.Context) error { fmt.Println("-- After --") return nil } // true: go run app.go helpと打ってもhelpが出なくなる app.HideHelp = true app.Run(os.Args) }
実行
# そのまま実行
$ go run src/script/cli_20/main.go hoge fuga piyo
-- Before --
-- Action --
c.NArg() : 3
c.Args() : [hoge fuga piyo]
c.Args().Get(0) : hoge
c.Args()[0] : hoge
c.FlagNames : []
cli_20 version 1.2.1
-- After --
# help
$ go run main.go help
-- Before --
NAME:
cli_20 - cli_20 sample
USAGE:
main [global options] command [command options] [arguments...]
VERSION:
1.2.1
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help
--version, -v print the version
-- After --
# `app.HideHelp = true` なのでhelpは引数として見なされる
$ go run src/script/cli_20/main.go help
-- Before --
-- Action --
c.NArg() : 1
c.Args() : [help]
c.Args().Get(0) : help
c.Args()[0] : help
c.String() :
cli_20 version 1.2.1
-- After --
context
c.XXXXで取れるcontextについてはこの辺を見るとかなりたくさんありますね。
https://godoc.org/github.com/urfave/cli#Context
3. Flags 使ってみる
flagsは-lang englishや-l english的なオプションを受け取るためのものです。
ショートオプションや省略時のdefault optionも指定できます。
command、option、flagとは?というところは以下が大変参考になります。
Go言語によるCLIツール開発とUNIX哲学について
使ってみる
src/script/cli_30/main.go
package main import ( "fmt" "os" "github.com/urfave/cli" ) func main() { app := cli.NewApp() app.Name = "cli_30" app.Usage = "cli_30 sample" app.Version = "1.2.1" os.Setenv("SAMPLE_ENV", "sample env dayo") // flags app.Flags = []cli.Flag{ cli.StringFlag{ Name: "lang, l", Value: "english", Usage: "language for the greeting", }, cli.StringFlag{ Name: "meridian, m", Value: "AM", Usage: "meridian for the greeting", }, cli.StringFlag{ Name: "time, t", Value: "07:00", // ``で囲むとhelp時のPlaceholderとしても使える // https://github.com/urfave/cli#placeholder-values Usage: "`your time` for the greeting", }, cli.StringFlag{ Name: "aaa, a", Value: "sample", // default値をValueからではなくEnvから取る EnvVar: "SAMPLE_ENV", }, } // action app.Action = func(c *cli.Context) error { fmt.Println("-- Action --") fmt.Printf("c.GlobalFlagNames() : %+v\n", c.GlobalFlagNames()) fmt.Printf("c.String(\"lang\") : %+v\n", c.String("lang")) fmt.Printf("c.String(\"m\") : %+v\n", c.String("m")) fmt.Printf("c.String(\"time\") : %+v\n", c.String("time")) fmt.Printf("c.String(\"a\") : %+v\n", c.String("a")) return nil } app.Run(os.Args) }
実行
# 実行
# 引数のkey:valueはスペースでもイコールでもいける
$ go run src/script/cli_30/main.go -l hoge -m=PM
-- Action --
c.GlobalFlagNames() : [aaa lang meridian time]
c.String("lang") : hoge
c.String("m") : PM
c.String("time") : 07:00
c.String("a") : sample env dayo
# help
$ go run src/script/cli_30/main.go -h
NAME:
cli_30 - cli_30 sample
USAGE:
main [global options] command [command options] [arguments...]
VERSION:
1.2.1
COMMANDS:
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--aaa value, -a value (default: "sample") [$SAMPLE_ENV]
--lang value, -l value language for the greeting (default: "english")
--meridian value, -m value meridian for the greeting (default: "AM")
--time your time, -t your time your time for the greeting (default: "07:00")
--help, -h show help
--version, -v print the version
Flagの種類
Flagとして設定できる種類がいくつかあるのでその例です。
StringFlagやBoolFlagが指定できます。
src/script/cli_31/main.go
package main import ( "fmt" "os" "github.com/urfave/cli" ) func main() { app := cli.NewApp() app.Name = "cli_31" app.Usage = "cli_31 sample" app.Version = "1.2.1" // flags app.Flags = []cli.Flag{ // StringFlag cli.StringFlag{ Name: "name, n", Value: "tarou", Usage: "your name", }, // BoolFlag cli.BoolFlag{ Name: "gay, g", Usage: "are you gay boy?", }, } // action app.Action = func(c *cli.Context) error { fmt.Println("-- Action --") fmt.Printf("c.GlobalFlagNames() : %+v\n", c.GlobalFlagNames()) fmt.Printf("c.String(\"name\") : %+v\n", c.String("name")) fmt.Printf("c.String(\"g\") : %+v\n", c.Bool("g")) return nil } app.Run(os.Args) }
実行
# 指定無しで叩いてみる
$ go run src/script/cli_31/main.go
-- Action --
c.GlobalFlagNames() : [name gay]
c.String("name") : tarou
c.String("g") : false
# 指定有りで叩いてみる
# BoolFlagは、オプションを指定することでtrueとなるFlag
$ go run src/script/cli_31/main.go --name hoge -g
-- Action --
c.GlobalFlagNames() : [name gay]
c.String("name") : hoge
c.String("g") : true
4. Subcommands でいろいろ
公式の通りですが、
git-like のようなコマンドが設定できます。
普通に使ってみる
まずは公式のお試し
src/script/cli_40/main.go
package main import ( "fmt" "os" "github.com/urfave/cli" ) func main() { app := cli.NewApp() app.Name = "cli_40" app.Usage = "cli_40 sample" // command action // これまでのActionとは違い、flagsごとの挙動(Action)が設定できる app.Commands = []cli.Command{ // `go run cli_40/main.go add パラメータ`でactionするコマンド { Name: "add", Aliases: []string{"a"}, Usage: "add a task to the list", Action: func(c *cli.Context) error { fmt.Println("added task: ", c.Args().First()) return nil }, }, // `go run cli_40/main.go complete パラメータ`でactionするコマンド { Name: "complete", Aliases: []string{"c"}, Usage: "complete a task on the list", Action: func(c *cli.Context) error { fmt.Println("completed task: ", c.Args().First()) return nil }, }, { Name: "template", Aliases: []string{"t"}, Usage: "options for task templates", Subcommands: []cli.Command{ // `go run cli_40/main.go template add パラメータ`でactionするコマンド { Name: "add", Usage: "add a new template", Action: func(c *cli.Context) error { fmt.Println("new task template: ", c.Args().First()) return nil }, }, // `go run cli_40/main.go template remove パラメータ`でactionするコマンド { Name: "remove", Usage: "remove an existing template", Action: func(c *cli.Context) error { fmt.Println("removed task template: ", c.Args().First()) return nil }, }, }, }, } app.Run(os.Args) }
実行
実行してみるとこんな感じ
# addコマンドとパラメータで実行
$ go run src/script/cli_40/main.go t add hoge
added task: hoge
# t(template)コマンドとaddサブコマンドで実行
$ go run src/script/cli_40/main.go t t add hoge
new task template: hoge
$ go run src/script/cli_40/main.go -h
NAME:
cli_04 - cli_04 sample
USAGE:
main [global options] command [command options] [arguments...]
VERSION:
0.0.0
COMMANDS:
add, a add a task to the list
complete, c complete a task on the list
template, t options for task templates
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help
--version, -v print the version
COMMANDSのtemplateのSubcommandsが表示されないじゃないかーと思いますが
templateまで打って-hすればちゃんと出ます。
かしこい!
$ go run cli_40/main.go t -h
NAME:
cli_04 template - options for task templates
USAGE:
cli_04 template command [command options] [arguments...]
COMMANDS:
add add a new template
remove remove an existing template
OPTIONS:
--help, -h show help
before/action/afterとの関係を見る
COMMANDS(のAction)とbefore/action/after らへんはどういう関係で動くか。
src/script/cli_41/main.go
import (
"fmt"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Name = "cli_41"
app.Usage = "cli_41 sample"
// before
app.Before = func(c *cli.Context) error {
fmt.Println("-- Before --")
return nil
}
// command action
app.Commands = []cli.Command{
// `go run cli_04/main.go sample パラメータ`でactionするコマンド
{
Name: "sample",
Aliases: []string{"s"},
Usage: "sample task",
Action: func(c *cli.Context) error {
fmt.Println("-- Sample Action --")
fmt.Println("sample task: ", c.Args().First())
return nil
},
},
}
// action
app.Action = func(c *cli.Context) error {
fmt.Println("-- Nomal Action --")
return nil
}
// after
app.After = func(c *cli.Context) error {
fmt.Println("-- After --")
return nil
}
app.Run(os.Args)
}
実行
COMMANDSが渡されたときはapp.Actionは実行されず、
COMMANDSが何も渡されないとapp.Actionが実行されます。
$ go run src/script/cli_41/main.go sample hoge -- Before -- -- Sample Action -- sample task: hoge -- After -- $ go run src/script/cli_41/main.go -- Before -- -- Nomal Action -- -- After --
c.Commandを見てみる
COMMANDS に指定したAction: func内で使えるc.Commandを見てみます。
こんなのが使えるよー程度ですね。
src/script/cli_42/main.go
package main import ( "fmt" "os" "github.com/urfave/cli" ) func main() { app := cli.NewApp() app.Name = "cli_42" app.Usage = "cli_42 sample" // command action app.Commands = []cli.Command{ { Name: "sample", Aliases: []string{"s"}, Usage: "sample task", Action: func(c *cli.Context) error { fmt.Println("-- Sample Action --") fmt.Printf("c.Command.FullName() : %+v\n", c.Command.FullName()) fmt.Printf("c.Command.HasName(\"sample\") : %+v\n", c.Command.HasName("sample")) fmt.Printf("c.Command.Names() : %+v\n", c.Command.Names()) fmt.Printf("c.Command.VisibleFlags() : %+v\n", c.Command.VisibleFlags()) return nil }, }, } app.Run(os.Args) }
実行
$ go run src/script/cli_42/main.go sample hoge
-- Sample Action --
c.Command.FullName() : sample
c.Command.HasName("sample") : true
c.Command.Names() : [sample s]
c.Command.VisibleFlags() : [--help, -h show help]
Subcommands x Flags
次はSubcommands x Flagsの例です。
Flags単独の例は前述してますが、各Subcommandsごとにも設定できます。
src/script/cli_43/main.go
import (
"fmt"
"os"
"github.com/urfave/cli"
)
func main() {
app := cli.NewApp()
app.Name = "cli_43"
app.Usage = "cli_43 sample"
// command action
app.Commands = []cli.Command{
// addコマンドに対してFlagsを設定
{
Name: "add",
Aliases: []string{"a"},
Usage: "add a task to the list",
Action: func(c *cli.Context) error {
fmt.Println("added task: ", c.Args().First())
fmt.Println("json : ", c.String("j"))
fmt.Println("exec : ", c.String("e"))
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "json, j",
Value: "add.json",
},
cli.BoolFlag{
Name: "exec, e",
},
},
},
// completeコマンドに対してFlagsを設定
{
Name: "complete",
Aliases: []string{"c"},
Usage: "complete a task on the list",
Action: func(c *cli.Context) error {
fmt.Println("completed task: ", c.Args().First())
fmt.Println("csv : ", c.String("c"))
fmt.Println("exec : ", c.String("e"))
return nil
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "csv, c",
Value: "add.json",
},
cli.BoolFlag{
Name: "exec, e",
},
},
},
}
app.Run(os.Args)
}
実行
# addコマンドを実行。addコマンドに指定したFlagsを指定 $ go run src/script/cli_43/main.go add -j test.json -e added task: json : test.json exec : true # addコマンドを実行。completeコマンドに指定したFlagsを指定 # オプションが違うよ、という事でhelpが出る $ go run src/script/cli_43/main.go add -c test.csv -e Incorrect Usage: flag provided but not defined: -c NAME: main add - add a task to the list USAGE: main add [command options] [arguments...] OPTIONS: --json value, -j value (default: "add.json") --exec, -e # completeコマンドを実行。addコマンドに指定したFlagsを指定 $ go run src/script/cli_43/main.go complete -c test.csv completed task: csv : test.csv exec : false
おわり
urfave/cli いろいろてんこもりで便利ですね。
ここまでがっつり機能いらないよって場合は、標準のflag パッケージの方を使ってサクっと作るのも良さそうです
\(^o^)/