-file — имя файла с изображением;-dest — директория для сохранения обработанного изображения;-w — ширина обработанного изображения;-thumb — дополнительный параметр, если нужно создать миниатюру.flag и как использовать аргументы командной строки для конфигурирования приложения. Вы узнаете:$ imgcnv -thumb -w 1024 -file "./in/img0001.jpg" -dest "/home/user/images" flag, то на помощь придут сторонние библиотеки. Обзор одной из них вы также найдёте в этом уроке.-x). Стандарт GNU расширяет POSIX за счёт использования имён с двойным дефисом (--name). А для Windows распространён вариант написания параметров через слеш (/x). Объединяет эти соглашения, пожалуй, только то, что значение параметра следует заключать в кавычки, если оно содержит пробел (-p "значение с пробелами").os.Args []string. Она содержит все параметры командной строки, начиная с имени запущенного приложения. При этом вы можете работать с параметрами и интерпретировать их как вам угодно.package main
import (
"fmt"
"os"
)
func main() {
// первый аргумент — имя запущенного файла
fmt.Printf("Command: %v\n", os.Args[0])
// выведем остальные параметры
for i, v := range os.Args[1:] {
fmt.Println(i+1, v)
}
} go run main.go -thumb -w 1024 -file "./in/img0001.jpg" -dest "/home/user/images", то компилятор создаст исполняемый файл во временной директории и передаст ему указанные параметры. В результате получится примерно следующее:Command: /tmp/go-build3250572125/b001/exe/imgcnv
1 -thumb
2 -w
3 1024
4 -file
5 ./in/img0001.jpg
6 -dest
7 /home/user/images os.Args. Но если есть несколько флагов, которые могут определяться в любом порядке, то разбор os.Args становится гораздо сложнее. Придётся искать каждый параметр в списке доступных аргументов, а ещё учитывать ошибочное указание флагов. flag стандартной библиотеки, который содержит функции для парсинга аргументов командной строки.-flag // логический флаг без значения
-flag=x // значения можно указывать через = или пробел
-flag x // не подходит для логических флагов --flag и -flag эквивалентны. Чаще всего однобуквенные флаги пишут с одним дефисом, а длинные формы — с двумя и знаком равенства, но для пакета flag формы -f value и --f=value означают одно и то же. 1, 0, t, f, true, false. Если у логического флага не указано значение, то ему присваивается true. -h и --help пакет добавляет в программу автоматически и при обращении к ним выводит список доступных флагов. Например, ./imgcnv -h будет выводить все флаги, которые вы определите с помощью пакета flag.-file:package main
import (
"flag"
"fmt"
)
func main() {
// указываем имя флага, значение по умолчанию и описание
imgFile := flag.String("file", "", "input image file")
// делаем разбор командной строки
flag.Parse()
fmt.Println("Image file:", *imgFile)
} --help, выведет:$ ./imgcnv --help
Usage of ./imgcnv:
-file string
input image file --file, получим:$ ./imgcnv -file /home/user/input/img001.png
Image file: /home/user/input/img001.png flag.Bool(name string, value bool, usage string) *bool,Int(name string, value int, usage string) *int,String(name string, value string, usage string) *string.int64, float64 и других типов. usage. А возвращают адрес переменной, с которой связывается значение флага. flag.Parse(), а до этого переменным присвоены значения по умолчанию. flag.Parse() делает парсинг аргументов командной строки os.Args[1:] в соответствии с декларациями флагов: если флаг был определён, он будет распарсен соответствующим образом. Затем функция заполняет значениями связанные переменные, после чего их можно использовать в логике кода.package main
import (
"flag"
"fmt"
)
func main() {
imgFile := flag.String("file", "", "input image file")
destDir := flag.String("dest", "./output", "destination folder")
width := flag.Int("w", 1024, "width of the image")
isThumb := flag.Bool("thumb", false, "create thumb")
// разбор командной строки
flag.Parse()
fmt.Println("Image file:", *imgFile)
fmt.Println("Destination folder:", *destDir)
fmt.Println("Width:", *width)
fmt.Println("Thumbs:", *isThumb)
} imgcnv -thumb -w 1024 -file "./in/img0001.jpg" -dest "/home/user/images".BoolVar(p *bool, name string, value bool, usage string),IntVar(p *int, name string, value int, usage string),StringVar(p *string, name string, value string, usage string).var options struct {
width int
thumb bool
}
// связывать так
flag.IntVar(&options.width, "width", 1024, "width of the image")
flag.BoolVar(&config.thumb, "thumb", false, "create thumb") init()-функции пакета или сразу присваивать значения глобальным переменным:var (
width *int
thumb *bool
)
func init() {
// используем init-функцию
width = flag.Int("width", 1024, "width of the image")
thumb = flag.Bool("thumb", false, "create thumb")
} // сразу используем глобальные переменные
var (
width = flag.Int("width", 1024, "width of the image")
thumb = flag.Bool("thumb", false, "create thumb")
) main() останется только вызвать flag.Parse(). Вызывать flag.Parse() в init()-функциях не следует, потому что порядок выполнения этих функций трудно предсказать.flag.Parse() обрабатывает флаги. Но не у всех аргументов командной строки обязательно должен быть синтаксис и семантика флагов. -file, а файлы для обработки указать после флагов. Тогда у имён файлов не будет семантики флага — это просто аргументы. flag.Parse() встречает аргумент, не соответствующий стандарту парсинга, такой аргумент и все последующие становятся для неё позиционными. Они рассматриваются как слайс строк, и их можно получить функциями flag.Arg() и flag.Args():Arg(i int) string возвращает i-й позиционный аргумент в виде строки,Args() []string возвращает все позиционные аргументы в виде слайса строк.func main() {
destDir := flag.String("dest", "./output", "destination folder")
width := flag.Int("w", 1024, "width of the image")
isThumb := flag.Bool("thumb", false, "create thumb")
flag.Parse()
// получаем список файлов
for i, v := range flag.Args() {
fmt.Printf("Image file (%d):\r\n", i, v)
}
fmt.Println("Destination folder:", *destDir)
fmt.Println("Width:", *width)
fmt.Println("Thumbs:", *isThumb)
} img cnv -w 1024 -dest "/home/user/images" "./in/img0001.jpg"
img filter -gray -dest "./output" "./in/img0003.jpg" flag позволяет создавать наборы флагов. Нужно это в основном для реализации концепции подкоманд (subcommands). Впервые такую форму применили разработчики утилиты git (git add, git commit), после чего она стала очень популярной.flag.FlagSet представляет собой набор декларированных флагов. Верхнеуровневые функции пакета вроде flag.Int() работают на наборе flag.CommandLine, который определён так:var CommandLine = NewFlagSet(os.Args[0], ExitOnError) flag.NewFlagSet(name string, errorHandling ErrorHandling) принимает аргументами имя нового набора и стратегию поведения в случае ошибки в методе Parse():ContinueOnError() — вернёт ошибку;ExitOnError() — прекратит работу программы;PanicOnError() — создаст панику.NewFlagSet() возвращает новый набор флагов, для которого можно задать свои правила обработки.img должны быть подкоманды cnv и filter со своими наборами флагов: // декларируем наборы флагов для подкоманд
cnvFlags := flag.NewFlagSet("cnv", flag.ExitOnError)
filterFlags := flag.NewFlagSet("filter", flag.ExitOnError)
// декларируем флаги набора cnvFlags
destDir := cnvFlags.String("dest", "./output", "destination folder")
width := cnvFlags.Int("w", 1024, "width of the image")
isThumb := cnvFlags.Bool("thumb", false, "create thumb")
// флаги набора filterFlags
isGray := filterFlags.Bool("gray", false, "convert to grayscale")
isSepia := filterFlags.Bool("sepia", false, "convert to sepia")
// проверяем, задана ли подкоманда
// os.Arg[0] имя команды
// os.Arg[1] имя подкоманды
if len(os.Args) < 2 {
fmt.Println("set or get subcommand required")
os.Exit(1)
}
// в зависимости от переданной подкоманды
// делаем парсинг флагов соответствующего набора,
// передаём функции FlagSet.Parse() аргументы командной строки
// os.Args[2:] содержит все аргументы,
// следующие за os.Args[1], за именем подкоманды
switch os.Args[1] {
case "cnv":
cnvFlags.Parse(os.Args[2:])
case "filter":
filterFlags.Parse(os.Args[2:])
default:
// PrintDefaults выводит параметры командной строки
flag.PrintDefaults()
os.Exit(1)
}
// проверяем, какой набор флагов использовался,
// то есть какая подкоманда была передана,
// функция FlagSet.Parsed() возвращает false, если
// парсинг флагов набора не проводился
if cnvFlags.Parsed() {
// логика для img cnv
}
if filterFlags.Parsed() {
// логика для img filter
} func Func(name, usage string, fn func(string) error) flag.Func() принимает в качестве аргументов имя флага, информативную строку usage и функцию-обработчик. Если при парсинге параметров командной строки встречается флаг с указанным именем, его значение передаётся строкой функции-обработчику.imgcnv -effects=rot90,mirh,mirv,rot270. Чтобы это сделать, нужно дописать код:// готовим переменную для аргументов
var effects []string
// декларируем функцию-обработчик
flag.Func("effects", "Rotation and mirror", func(flagValue string) error {
// разбиваем значение флага на слайс строк через запятую
// и заливаем в переменную
effects = strings.Split(flagValue, ",")
return nil
})
// запускаем парсинг
flag.Parse() flag.Func() предлагает указать функцию. Но в пакете flag также есть инструмент, основанный на статической типизации. Функция Var(value Value, name string, usage string) допускает процессинг флагов в произвольные пользовательские типы, удовлетворяющие интерфейсу flag.Value.type Value interface {
String() string
Set(string) error
} -effects=rot90,mirh,mirv,rot270 с использованием функции flag.Var(). Допустим, уже есть тип:type Options struct {
width int
thumb bool
effects []string
} flag.Value:// String должен уметь сериализовать переменную типа в строку.
func (o *Options) String() string {
return fmt.Sprint(strings.Join(o.effects, ","))
}
// Set связывает переменную типа со значением флага
// и устанавливает правила парсинга для пользовательского типа.
func (o *Options) Set(flagValue string) error {
o.effects = strings.Split(flagValue, ",")
return nil
} flag.Var():options := new(Options)
// декларируем парсинг флага effects в переменную типа Options
flag.Var(options, "effects", "Rotation and mirror")
// запускаем парсинг
flag.Parse() flag.Func().flag прост в применении, и часто его функций достаточно для решения рабочих задач. А если нет — в Go-сообществе есть другие популярные библиотеки для работы с параметрами командной строки.pflag:pflag стандарта POSIX рекомендациям GNU;uint8, uint16, int32;ip, ip mask, ip net, count и всех типов слайсов;shorthand, deprecated, hidden.var myIp *net.IP
myIp = pflag.IP(ip, net.ParseIP("192.168.0.1"), "Host IP address") pflag позволяет использовать такой синтаксис:-aon "jack"
-aon="jack"
-aon"jack"
-aonjack pflag предоставляет вспомогательную функцию MarkDeprecated() для таких ситуаций:// помечает параметр badflag вышедшим из употребления,
// предлагает новый флаг des-detail
flag.CommandLine.MarkDeprecated("badflag", "please use --des-detail instead") pflag можно использовать вместе со стандартным flag:import (
goflag "flag"
flag "github.com/spf13/pflag"
)
var ip *int = flag.Int("flagname", 1234, "help message for flagname")
func main() {
// для использования флагов stdlib/flag
// набор нужно добавить к pflag.FlagSet
flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
flag.Parse()
} 8080. http_alt. Он часто используется для запуска и тестирования HTTP-сервисов, что резко повышает вероятность ситуации, когда порт окажется занят и наш сервис не сможет запуститься.cmd/skill/flags.go. Должна получиться такая структура проекта: > ~/dev/alice-skill
|
|--- cmd
| |--- skill
| |--- flags.go
| |--- main.go
| |--- main_test.go
|--- internal
|--- go.mod
|--- go.sum
flags.go добавлен непосредственно в директорию с исходным кодом, потому что аргументы, которые будут описаны в этом файле, имеют отношение только к исполняемому приложению. Хорошая практика — ограничивать методы конфигурации только конфигурируемым кодом.flags.go напишем:package main
import (
"flag"
)
// неэкспортированная переменная flagRunAddr содержит адрес и порт для запуска сервера
var flagRunAddr string
// parseFlags обрабатывает аргументы командной строки
// и сохраняет их значения в соответствующих переменных
func parseFlags() {
// регистрируем переменную flagRunAddr
// как аргумент -a со значением :8080 по умолчанию
flag.StringVar(&flagRunAddr, "a", ":8080", "address and port to run server")
// парсим переданные серверу аргументы в зарегистрированные переменные
flag.Parse()
} cmd/skill/main.go так, чтобы использовать переменную flagRunAddr в качестве источника адреса:func main() {
// обрабатываем аргументы командной строки
parseFlags()
if err := run(); err != nil {
panic(err)
}
}
func run() error {
fmt.Println("Running server on", flagRunAddr)
return http.ListenAndServe(flagRunAddr, http.HandlerFunc(webhook))
} -a:$ go build -o skill
$ ./skill
Running server on :8080
$ ./skill -a :8081
Running server on :8081 -a сервер, как и раньше, будет запускаться на локальном порту 8080. flag.Cobra.pflag.