はじめに
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^)/