Предыдущая часть — Golang: Go in Practice – заметки на полях, часть 1 – введение.
Стандартная библиотека Go включает в себя пакеты для создания приложения с поддержкой опций командной строки.
В отличии от стандартных, принятых в Linux/BSD, стилей — в пакете из стандартной библиотеке Go используются одинарные или двойные дефисы как взаимозаменяемые.
Т.е. тут не будет возможность указать несколько опций, используя формат tar -xvpf — для Go xvpf будет являться одной опцией, а не набором опций для tar — -x, -v, -p и -f.
Содержание
Пакет flag
Пакет flag поддерживает несколько вариантов добавления опций.
flag.String()
Вариант первый — с помощью flag.String():
package main
import (
"flag"
"fmt"
)
func main() {
// to save the 'name' option value
var name = flag.String("name", "World", "The 'name' option value")
// parse flag's options
flag.Parse()
// print the 'name'`s value
fmt.Printf("Hello, %s\n", *name)
}
Тут в var name создаём переменную, которая ссылается на объект flag.String, которому передаются имя флага («name«), значение по умолчанию («World«), и строка для отображения в --help.
Вызываем программу без опций:
[simterm]
$ go run cli.go Hello, World
[/simterm]
С опцией name, которой передаём значение username:
[simterm]
$ go run cli.go --name username Hello, username
[/simterm]
Или с использованием одного дефиса:
[simterm]
$ go run cli.go -name username Hello, username
[/simterm]
И без передачи значения:
[simterm]
$ go run cli.go -name
flag needs an argument: -name
Usage of /tmp/go-build471367643/b001/exe/cli:
-name string
The 'name' option value (default "World")
exit status 2
[/simterm]
Указатели
Пару слов об указателях. Есть достаточно понятный разбор указателей в C тут>>> — разницы между указателями в Go и C особо нет, так что можно ознакомиться.
Если кратко — в примере выше мы выполняем fmt.Printf("Hello, %s\n", *name) — в модификатор %s подставляем значение, которое находится по адресу памяти, которое хранится в переменной name (которая, собственно, тут является указателем).
Можно получить это значение так:
...
// print the 'name'`s value
fmt.Printf("Hello, %s\n", *name)
fmt.Printf("The 'name'`s address is %p\n", &name)
fmt.Printf("The 'name'`s value is %p\n", name)
}
Выполняем:
[simterm]
$ go run cli.go Hello, World The 'name'`s address is 0xc00000c028 The 'name'`s value is 0xc00000e200
[/simterm]
Тут 0xc00000c028 — адрес самой переменной name, а 0xc00000e200 — адрес объекта flag.String().
Документация по модификаторам Printf() — тут>>>.
flag.BoolVar()
Другой вариант использования флагов — с помощью flag.BoolVar():
package main
import (
"flag"
"fmt"
)
var rus bool
func init() {
flag.BoolVar(&rus, "russian", false, "Use Russian language")
flag.BoolVar(&rus, "r", false, "Use Russian language")
}
func main() {
// to save the 'name' option value
var name = flag.String("name", "World", "The 'name' option value")
// parse flag's options
flag.Parse()
// print the 'name'`s value in Eng or Ru depending on the 'lang'`s value
if rus == true {
fmt.Printf("Привет, %s\n", *name)
} else {
fmt.Printf("Hello, %s\n", *name)
}
}
Тут добавляем объявление переменной rus типа boolean, затем вызываем init-функцию, в которой с помощью flag.BoolVar() определяем значение для rus в true или false, а затем в if/else проверяем значение и выбираем действие:
[simterm]
$ go run cli.go Hello, World $ go run cli.go -r Привет, World $ go run cli.go -russian Привет, World
[/simterm]
Что бы вывести аргументы программы — можно использовать os.Args:
package main
import (
"flag"
"fmt"
"os"
)
var rus bool
func init() {
flag.BoolVar(&rus, "russian", false, "Use Russian language")
flag.BoolVar(&rus, "r", false, "Use Russian language")
}
func main() {
// to save the 'name' option value
var name = flag.String("name", "World", "The 'name' option value")
// parse flag's options
flag.Parse()
fmt.Println("Args: ", os.Args)
// print the 'name'`s value in Eng or Ru depending on the 'lang'`s value
if rus == true {
fmt.Printf("Привет, %s\n", *name)
} else {
fmt.Printf("Hello, %s\n", *name)
}
}
Результат:
[simterm]
$ go run cli.go -name user -r Args: [/tmp/go-build005430960/b001/exe/cli -name user -r] Привет, user
[/simterm]
Или получить срез — всё, кроме нулевого элемента, т.е. имени самой программы:
...
fmt.Println("Args: ", os.Args[1:])
...
Запускаем:
[simterm]
$ go run cli.go -name user -r Args: [-name user -r] Привет, user
[/simterm]
UNIX-style опции
gnuflag
Для создания нормальных опций — существует несколько внешних библиотек, одна из них — gnuflag.
Для импорта этого пакета потребуется Bazaar — устанавливаем:
[simterm]
$ sudo pacman -S bzr
[/simterm]
Импортируем пакет:
[simterm]
$ go get launchpad.net/gnuflag
[/simterm]
Первое — gnuflag позволяет использовать группировку опций.
Т.е. — при использовании flag, если вызывать опции в виде -ab они не будут интерпретированы как две опции -a и -b.
Например — добавим в предыдущий скрипт опцию -с:
package main
import (
"flag"
"fmt"
"os"
"strings"
)
var rus bool
var ca bool
func init() {
flag.BoolVar(&rus, "russian", false, "Use Russian language")
flag.BoolVar(&rus, "r", false, "Use Russian language")
flag.BoolVar(&ca, "capital", false, "Use CAPITAL")
flag.BoolVar(&ca, "c", false, "Use CAPITAL")
}
func main() {
// to save the 'name' option value
var name = flag.String("name", "World", "The 'name' option value")
// parse flag's options
flag.Parse()
fmt.Println("Args: ", os.Args[1:])
// concatenate strings
hello_ru := "Привет " + *name
hello_en := "Hello " + *name
if rus == true {
//fmt.Printf("Привет, %s\n", *name)
if ca == true {
fmt.Println(strings.ToUpper(hello_ru))
} else {
fmt.Println(hello_ru)
}
} else {
//fmt.Printf("Hello, %s\n", &name)
if ca == true {
fmt.Println(strings.ToUpper(hello_en))
} else {
fmt.Println(hello_en)
}
}
}
Вызываем с передачей опций вместе:
[simterm]
$ go run cli.go -rc
flag provided but not defined: -rc
Usage of /tmp/go-build528120016/b001/exe/cli:
-c Use CAPITAL
-capital
Use CAPITAL
-name string
The 'name' option value (default "World")
-r Use Russian language
-russian
Use Russian language
exit status 2
[/simterm]
Получаем ошибку.
По отдельности — они будут работать:
[simterm]
$ go run cli.go -r -c Args: [-r -c] ПРИВЕТ WORLD
[/simterm]
Использование gnuflag аналогично flag, с той разницей, что в Parse() передаётся дополнительный аргумент для allowIntersperse.
allowIntersperse может принимать true или false, и определяет — будет ли Parse() проверять флаги после первого позиционного аргумента.
Для начала — пример того, как gnuflag работает с группировкой опций.
Используем пример с flags, но заменим flags на gnuflags:
package main
import (
"fmt"
"launchpad.net/gnuflag"
"os"
"strings"
)
var rus bool
var ca bool
//var name string
func init() {
gnuflag.BoolVar(&rus, "russian", false, "Use Russian language")
gnuflag.BoolVar(&rus, "r", false, "Use Russian language")
gnuflag.BoolVar(&ca, "capital", false, "Use CAPITAL")
gnuflag.BoolVar(&ca, "c", false, "Use CAPITAL")
}
func main() {
var name = gnuflag.String("name", "World", "The 'name' option value")
// parse flag's options
gnuflag.Parse(false)
fmt.Println(os.Args)
hello_ru := "Привет " + *name
hello_en := "Hello " + *name
fmt.Println(ca)
// print the 'name'`s value in Eng or Ru depending on the 'lang'`s value
if rus == true {
//fmt.Printf("Привет, %s\n", *name)
if ca == true {
fmt.Println(strings.ToUpper(hello_ru))
} else {
fmt.Println(hello_ru)
}
} else {
//fmt.Printf("Hello, %s\n", &name)
if ca == true {
fmt.Println(strings.ToUpper(hello_en))
} else {
fmt.Println(hello_en)
}
}
}
Проверяем:
[simterm]
$ go run gnuflag_cli.go -rc --name User [/tmp/go-build804626272/b001/exe/gnuflag_cli -rc --name User] true ПРИВЕТ USER
[/simterm]
Тут обработались обе опции — и -r, и -c.
Теперь рассмотрим значение allowIntersperse.
В случае с flags — он не будет проверять опции после первого позиционного аргумента:
...
func main() {
// to save the 'name' option value
var name = flag.String("name", "World", "The 'name' option value")
// parse flag's options
flag.Parse()
fmt.Println("Args: ", os.Args[1:])
fmt.Printf("First arg: %s\n", os.Args[1])
hello_ru := "Привет " + *name
hello_en := "Hello " + *name
...
[simterm]
$ go run cli.go first -r Args: [first -r] First arg: first Hello World
[/simterm]
Опция -r игнорируется.
В случае с gnuflags, если в Parse() передаём false — он будет вести себя аналогично flags:
...
func main() {
var name = gnuflag.String("name", "World", "The 'name' option value")
// parse flag's options
//gnuflag.Parse(true)
gnuflag.Parse(false)
fmt.Println(os.Args)
fmt.Printf("First arg: %s\n", os.Args[1])
hello_ru := "Привет " + *name
hello_en := "Hello " + *name
...
Проверяем:
[simterm]
$ go run gnuflag_cli.go first -r [/tmp/go-build346296451/b001/exe/gnuflag_cli first -r] First arg: first false Hello World
[/simterm]
-r проигнорирована.
Передаём в Parse() значение true:
...
func main() {
var name = gnuflag.String("name", "World", "The 'name' option value")
// parse flag's options
gnuflag.Parse(true)
//gnuflag.Parse(false)
...
Проверяем:
[simterm]
$ go run gnuflag_cli.go first -r [/tmp/go-build024407980/b001/exe/gnuflag_cli first -r] First arg: first false Привет World
[/simterm]
Или все опции:
[simterm]
$ go run gnuflag_cli.go first -r --name User -c [/tmp/go-build386862510/b001/exe/gnuflag_cli first -r --name User -c] First arg: first true ПРИВЕТ USER
[/simterm]
Работает.
go-flags
go-flagsЕщё один пакет для работы с опциями командной строки — go-flags.
В отличии от flags и gnuflags — тут опции задаются в тегах структуры, например:
package main
import (
"fmt"
flags "github.com/jessevdk/go-flags"
)
var opts struct {
Name string `short:"n" long:"name" default:"World" description:"The Name option value"`
Rus bool `short:"r" long:"russian" description:"Use Russian language"`
}
func main() {
_, err := flags.Parse(&opts)
if err != nil {
panic(err)
}
hello_ru := "Привет " + opts.Name
hello_en := "Hello " + opts.Name
if opts.Rus == true {
fmt.Println(hello_ru)
} else {
fmt.Println(hello_en)
}
}
И примеры выполнения:
[simterm]
$ go run goflags.go Hello World $ go run goflags.go -r Привет World $ go run goflags.go -rn Username Привет Username $ go run goflags.go -r --name Username Привет Username $ go run goflags.go -h Usage: goflags [OPTIONS] Application Options: -n, --name= The Name option value (default: World) -r, --russian Use Russian language Help Options: -h, --help Show this help message panic: Usage: goflags [OPTIONS] Application Options: -n, --name= The Name option value (default: World) -r, --russian Use Russian language Help Options: -h, --help Show this help message
[/simterm]
cli.go
cli.gocli.go представляет собой фреймворк для создания CLI-приложений.
Используется в таких приложениях как Docker:
[simterm]
$ grep -r urfave docker-ce/ docker-ce/components/cli/vendor/github.com/opencontainers/runc/vendor.conf:github.com/urfave/cli d53eb991652b1d438abdd34ce4bfa3ef1539108e docker-ce/components/cli/vendor/github.com/moby/buildkit/vendor.conf:github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c docker-ce/components/cli/vendor/github.com/containerd/containerd/vendor.conf:github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c docker-ce/components/engine/vendor/github.com/opencontainers/runc/vendor.conf:github.com/urfave/cli d53eb991652b1d438abdd34ce4bfa3ef1539108e docker-ce/components/engine/vendor/github.com/moby/buildkit/vendor.conf:github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c docker-ce/components/engine/vendor/github.com/containerd/cri/vendor.conf:github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c docker-ce/components/engine/vendor/github.com/containerd/containerd/vendor.conf:github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
[/simterm]
Напишем такой пример:
package main
import (
"fmt"
"gopkg.in/urfave/cli.v1"
"os"
)
func main() {
app := cli.NewApp()
app.Name = "Example CLI"
app.Usage = "Print Hello messages"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "name, n",
Value: "World",
Usage: "The 'name' option value",
},
cli.BoolFlag{
Name: "russian, r",
Usage: "Use Russian language",
},
}
app.Action = func(c *cli.Context) error {
hello_ru := "Привет " + c.GlobalString("name")
hello_en := "Hello " + c.GlobalString("name")
rus := c.GlobalBool("russian")
if rus == true {
fmt.Printf("%s\n", hello_ru)
} else {
fmt.Printf("%s\n", hello_en)
}
return nil
}
app.Run(os.Args)
}
Тут:
cli.NewApp()— создаём новый экземляр приложения, доступ к которому будет через переменную app- в
app.Flagsзадаём опции приложенияStringFlag— строковые флагиName— короткое и длинное именаValue— значение по умолчаниюUsage— для вывода вhelp
BoolFlag— булев (логический) флагName— короткое и длинное именаUsage— для вывода вhelp- `Value` — не задаём явно: при вызове программы без опции — будет задано значение false, в противном случае true
app.Action()— задаём действие по умолчанию, аргументом передаём cli.Context с флагамиc.GlobalString("name")— получаем значение из опции name- аналогично —
c.GlobalBool("russian") app.Run(os.Args)— запускаем программу, передавая всё заданные в консоли аргументы
Проверяем:
[simterm]
$ go run go-cli-go.go Hello World $ go run go-cli-go.go -n User Hello User $ go run go-cli-go.go -n User -r Привет User
[/simterm]
Команды и подкоманды
Так же он поддерживает работу с командами и опциям команд.
Напишем программу, которая выполняет прямой и обратный отсчёт, и при этом умеет принимать аргумент, в котором можно указать число для начала отсчёта:
package main
import (
"fmt"
"gopkg.in/urfave/cli.v1"
"os"
)
func main() {
app := cli.NewApp()
app.Usage = "Print Hello messages"
app.Commands = []cli.Command{
{
Name: "up",
ShortName: "u",
Usage: "Count Up",
Flags: []cli.Flag{
cli.IntFlag{
Name: "stop, s",
Usage: "Value to count up to",
Value: 10,
},
},
Action: func(c *cli.Context) error {
start := c.Int("stop")
if start <= 0 {
fmt.Println("Stop cannot be negative")
}
for i := 1; i <= start; i++ {
fmt.Println("Value: ", i)
}
return nil
},
},
{
Name: "down",
ShortName: "d",
Usage: "Count Down",
Flags: []cli.Flag{
cli.IntFlag{
Name: "start, s",
Usage: "Value to count down from",
Value: 10,
},
},
Action: func(c *cli.Context) error {
start := c.Int("start")
if start <= 0 {
fmt.Println("Start cannot be negative")
}
for i := start; i >= 0; i-- {
fmt.Println("Value: ", i)
}
return nil
},
},
}
app.Run(os.Args)
}
Подробно тут уже останавливаться не буду, проверяем работу:
[simterm]
$ go run go-cli-go-subcmds.go
NAME:
go-cli-go-subcmds - Print Hello messages
USAGE:
go-cli-go-subcmds [global options] command [command options] [arguments...]
VERSION:
0.0.0
COMMANDS:
up, u Count Up
down, d Count Down
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help
--version, -v print the version
[/simterm]
[simterm]
$ go run go-cli-go-subcmds.go up
Value: 1
Value: 2
Value: 3
Value: 4
...
Value: 8
Value: 9
Value: 10
[/simterm]
[simterm]
$ go run go-cli-go-subcmds.go up -s 20
Value: 1
Value: 2
Value: 3
...
Value: 18
Value: 19
Value: 20
[/simterm]
[simterm]
$ go run go-cli-go-subcmds.go down
Value: 10
Value: 9
Value: 8
...
Value: 2
Value: 1
Value: 0
[/simterm]
[simterm]
$ go run go-cli-go-subcmds.go down -s 20
Value: 20
Value: 19
Value: 18
...
Value: 1
Value: 0
[/simterm]
В целом — этого достаточно, что использовать опции командной строки в своих программах.