Как использовать пакет Flag в Go

12 set 2022 7 min di lettura
Как использовать пакет Flag в Go
Indice dei contenuti

Введение

Утилиты командной строки редко готовы к использованию без дополнительной настройки. На большинстве платформ утилиты командной строки принимают флаги для настройки выполнения команд. Флаги — это строки, разделенные ключом и значением, которые добавляются после имени команды. Go позволяет создавать утилиты командной строки, которые принимают флаги, используя пакет flag из стандартной библиотеки.

В этом руководстве вы изучите различные способы использования пакета flag для создания различных типов утилит командной строки. Вы будете использовать флаг для управления выводом программы, вводя позиционные аргументы, в которые вы смешиваете флаги и другие данные, а затем реализуете вторичные команды.

Используйте флаг, чтобы изменить поведение программы

Использование пакета flag включает три шага: сначала определить переменные для захвата значений флагов, затем определить флаги, которые будут использоваться приложением Go, и, наконец, проанализировать флаги, предоставляемые приложению во время выполнения. Большинство функций в пакете flag включают определение флагов и привязку к определяемым пользователем переменным. Этап синтаксического анализа обрабатывается функцией Parse().

Вы создадите программу, определяющую логический флаг, изменяющий сообщение, которое будет напечатано на стандартном выводе. Если есть флаг -color, программа напечатает сообщение синим цветом. Если флаг не указан, сообщение будет напечатано без цвета.

Создайте новый файл с именем boolean.go:

nano boolean.go

Добавьте в файл следующий код для создания программы:

package main

 import (
 "flag"
 "fmt"
 )

 type Color string

 const (
 ColorBlack Color = "\u001b[30m"
 ColorRed = "\u001b[31m"
 ColorGreen = "\u001b[32m"
 ColorYellow = "\u001b[33m"
 ColorBlue = "\u001b[34m"
 ColorReset = "\u001b[0m"
 )

 func colorize(color Color, message string) {
 fmt.Println(string(color), message, string(ColorReset))
 }

 func main() {
 useColor:= flag.Bool("color", false, "display colorized output")
 flag.Parse()

 if *useColor {
 colorize(ColorBlue, "Hello, Noviello!")
 return
 }
 fmt.Println("Hello, Noviello!")
 }
логическое.go

В этом примере используются escape-последовательности ANSI, чтобы указать терминалу отображать цветной вывод. Это специализированные последовательности символов, поэтому имеет смысл определить для них новый тип. В этом примере мы назвали этот тип (тип) Color и определили тип (тип) как string. Затем мы определяем таблицу цветов (палитру), которая будет использоваться в следующем блоке const. Функция colorize, определенная после блока const, принимает одну из этих констант Color и string переменную для окрашивания сообщения. Затем он инструктирует терминал изменить цвет, сначала печатая escape-последовательность для требуемого цвета, затем печатает сообщение и, наконец, запрашивает терминал восстановить свой цвет, печатая специальную последовательность восстановления цвета.

Внутри main мы используем функцию flag.Bool для определения логического флага с именем color. Второй параметр этой функции, false, устанавливает значение по умолчанию для этого флага, если он не указан. Вопреки вашим ожиданиям, установка этого параметра в значение true не изменяет поведение таким образом, что установка флага приведет к тому, что оно станет ложным. Следовательно, значение этого параметра почти всегда false с булевыми флагами.

Последний параметр — это строка документации, которую можно распечатать как сообщение об использовании. Значение, возвращаемое этой функцией, является bool указателем. Функция flag.Parse в следующей строке использует этот указатель, чтобы установить базовую переменную bool для флагов, переданных пользователем. Затем мы можем управлять значением этого bool указателя, разыменовывая указатель. Используя это логическое значение, мы можем вызывать colorize, когда установлен флаг -color, и вызывать переменную fmt.Println когда флаг отсутствует.

Сохраните файл и запустите программу без флага:

go run boolean.go

Вы увидите следующий вывод:

Hello, Noviello!

Теперь снова запустите эту программу с флагом -color:

go run boolean.go -color

На выходе будет тот же текст, но на этот раз синего цвета.

Флаги — не единственные значения, которые передаются командам. Вы также можете отправить имена файлов или другие данные.

Работа с позиционными аргументами

Команды обычно принимают ряд аргументов, которые служат фокусом команды. Например, команда head, выводящая первые несколько строк файла, часто вызывается как head example.txt. Файл example.txt является позиционным аргументом в вызове команды head.

Функция Parse() будет продолжать анализировать флаги, с которыми она сталкивается, пока не встретит аргумент, не являющийся флагом. Пакет flag делает их доступными через функции Args() и Arg().

Чтобы проиллюстрировать это, вы создадите упрощенную повторную реализацию команды head, которая отображает первые несколько строк заданного файла:

Создайте новый файл с именем head.go и добавьте следующий код:

package main

 import (
 "bufio"
 "flag"
 "fmt"
 "io"
 "os"
 )

 func main() {
 var count int
 flag.IntVar(&count, "n", 5, "number of lines to read from the file")
 flag.Parse()

 var in io.Reader
 if filename:= flag.Arg(0); filename!= "" {
 f, err:= os.Open(filename)
 if err!= nil {
 fmt.Println("error opening file: err:", err)
 os.Exit(1)
 }
 defer f.Close()

 in = f
 } else {
 in = os.Stdin
 }

 buf:= bufio.NewScanner(in)

 for i:= 0; i < count; i++ {
 if!buf.Scan() {
 break
 }
 fmt.Println(buf.Text())
 }

 if err:= buf.Err(); err!= nil {
 fmt.Fprintln(os.Stderr, "error reading: err:", err)
 }
 }
голова.го

Во-первых, мы определяем переменную count для хранения количества строк, которые программа должна прочитать из файла. Затем мы определяем флаг -n с помощью flag.IntVar, отражая поведение исходной программы head. Эта функция позволяет нам передать указатель на переменную, в отличие от flag функций, которые не имеют суффикса Var. Помимо этой разницы, остальные параметры будут следовать за flag.IntVar его аналогом flag.Int: имя флага, значение по умолчанию и описание. Как и в предыдущем примере, мы вызываем flag.Parse() для обработки пользовательского ввода.

Следующий раздел читает файл. Сначала мы определяем переменную io.Reader, которая будет установлена ​​на файл, запрошенный пользователем, или стандартный ввод, переданный в программу. Внутри if мы используем функцию flag.Arg для доступа к первому позиционному аргументу после всех флагов. Если пользователь предоставил имя файла, оно будет установлено. В противном случае это будет пустая строка ( "" ). Когда есть имя файла, мы используем функцию os.Open, чтобы открыть этот файл и установить ранее определенный файл io.Reader для этого файла. В противном случае мы используем os.Stdin для чтения со стандартного ввода.

В последнем разделе используется *bufio.Scanner созданный с помощью bufio.NewScanner, для чтения строк io.Reader из переменной in. Мы проверяем значение count, используя цикл for, вызывая break, если сканирование строк buf.Scan дает false значение, указывающее, что количество строк меньше, чем число, запрошенное пользователем.

Запустите эту программу и отобразите содержимое файла, который вы только что написали, используя head.go в качестве аргумента файла:

go run head.go -- head.go

Разделитель -- это специальный флаг, распознаваемый пакетом flag, указывающий, что больше не следуют аргументы флага. Когда вы запускаете эту команду, вы получаете следующий вывод:

package main

 import (
 "bufio"
 "flag"

Используйте флаг -n, который вы определили, чтобы настроить объем вывода:

go run head.go -n 1 head.go

Это просто производит оператор пакета:

package main

Наконец, когда программа обнаруживает, что позиционные аргументы не предоставлены, она считывает ввод из стандартного ввода, как и в случае с head. Попробуйте запустить эту команду:

echo "fish\nlobsters\nsharks\nminnows" | go run head.go -n 3

Вы увидите вывод:

fish
 lobsters
 sharks

Поведение flag функций, которые вы видели до сих пор, ограничивалось проверкой всего вызова команды. Вам не всегда нужно такое поведение, особенно если вы пишете инструмент командной строки, поддерживающий вторичные команды.

Используйте FlagSet для реализации вторичных команд

Современные приложения командной строки часто реализуют «подкоманды», чтобы сгруппировать набор инструментов в одну команду. Наиболее известным инструментом, использующим эту модель, является git. При просмотре такой команды, как git init, git — это команда, а init — подкоманда git. Примечательной особенностью подкоманд является то, что каждая подкоманда может иметь собственный набор флагов.

Приложения Go могут поддерживать вторичные команды с собственным набором флагов, используя flag.(*FlagSet). Чтобы проиллюстрировать это, создайте программу, которая реализует команду, используя две подкоманды с разными флагами.

Создайте новый файл с именем subcommand.go и добавьте в него следующее содержимое:

package main

 import (
 "errors"
 "flag"
 "fmt"
 "os"
 )

 func NewGreetCommand() *GreetCommand {
 gc:= &GreetCommand{
 fs: flag.NewFlagSet("greet", flag.ContinueOnError),
 }

 gc.fs.StringVar(&gc.name, "name", "World", "name of the person to be greeted")

 return gc
 }

 type GreetCommand struct {
 fs *flag.FlagSet

 name string
 }

 func (g *GreetCommand) Name() string {
 return g.fs.Name()
 }

 func (g *GreetCommand) Init(args []string) error {
 return g.fs.Parse(args)
 }

 func (g *GreetCommand) Run() error {
 fmt.Println("Hello", g.name, "!")
 return nil
 }

 type Runner interface {
 Init([]string) error
 Run() error
 Name() string
 }

 func root(args []string) error {
 if len(args) < 1 {
 return errors.New("You must pass a sub-command")
 }

 cmds:= []Runner{
 NewGreetCommand(),
 }

 subcommand:= os.Args[1]

 for _, cmd:= range cmds {
 if cmd.Name() == subcommand {
 cmd.Init(os.Args[2:])
 return cmd.Run()
 }
 }

 return fmt.Errorf("Unknown subcommand: %s", subcommand)
 }

 func main() {
 if err:= root(os.Args[1:]); err!= nil {
 fmt.Println(err)
 os.Exit(1)
 }
 }
подкоманда.go

Эта программа разделена на несколько частей: main функция, root функция и отдельные функции для реализации второстепенной команды. main функция обрабатывает ошибки, возвращаемые командами. Если функция возвращает ошибку, оператор if перехватывает ее, печатает ошибку и программа завершает работу с кодом состояния 1, что указывает на то, что в остальной части операционной системы произошла ошибка. Внутри main мы передаем все аргументы, с которыми была вызвана root программа. Мы удаляем первый аргумент, который является именем программы (в предыдущих примерах ./subcommand ), вырезая сначала os.Args.

root функция определяет []Runner, в котором будут определены все подкоманды. Runner — это интерфейс подкоманды, который позволяет пользователю root получить имя подкоманды с помощью Name() и сравнить его с содержимым переменной subcommand. Как только мы находим правильную подкоманду после повторения переменной cmds, мы инициализируем подкоманду с остальными аргументами и вызываем метод Run() этой команды.

Мы определяем только одну подкоманду, хотя этот фреймворк легко позволяет нам создавать другие. С GreetCommand экземпляр создается с помощью NewGreetCommand в котором мы создаем новый *flag.FlagSet с помощью flag.NewFlagSet. flag.NewFlagSet принимает два аргумента: имя набора флагов и стратегию сообщения об ошибках синтаксического анализа. Имя *flag.FlagSet доступно с помощью метода flag.(*FlagSet).Name. Мы используем это в (*GreetCommand).Name(), чтобы имя подкоманды соответствовало имени, которое мы дали *flag.FlagSet. NewGreetCommand также определяет имя флага, аналогичное предыдущим примерам, но вместо этого требует, чтобы оно было вне допустимого *flag.FlagSet -name *GreetCommand, gc.fs Когда root вызывает метод Init() из *GreetCommand, мы передаем аргументы, предоставленные методу Parse поля *flag.FlagSet.

Подкоманды будет легче увидеть, если вы создадите эту программу, а затем запустите ее. Соберите программу:

go build subcommand.go

Теперь запустите программу без аргументов:

./subcommand

Вы увидите этот вывод:

You must pass a sub-command

Теперь запустите программу с помощью вторичной команды greet:

./subcommand greet

Это дает следующий результат:

Hello World!

Теперь используйте флаг -name с greet, чтобы указать имя:

./subcommand greet -name Noviello

Вы увидите этот вывод из программы:

Hello Noviello!

Этот пример иллюстрирует некоторые принципы, лежащие в основе того, как большие приложения командной строки могут быть структурированы в FlagSet предназначены для того, чтобы дать разработчикам больше контроля над тем, где и как обрабатываются флаги с помощью логики разбора флагов.

Вывод

Флаги делают ваши приложения более полезными в различных контекстах, поскольку они дают вашим пользователям контроль над тем, как работают программы. Важно предоставить пользователям полезные значения по умолчанию, но вы должны дать им возможность переопределить любые параметры, которые не работают в их ситуации. Вы видели, что пакет flag предлагает гибкие возможности для представления параметров конфигурации вашим пользователям. Вы можете выбрать несколько простых флагов или создать расширяемый набор подкоманд.

Buy me a coffeeBuy me a coffee

Supportaci se ti piacciono i nostri contenuti. Grazie.

Successivamente, completa il checkout per l'accesso completo a Noviello.it.
Bentornato! Accesso eseguito correttamente.
Ti sei abbonato con successo a Noviello.it.
Successo! Il tuo account è completamente attivato, ora hai accesso a tutti i contenuti.
Operazione riuscita. Le tue informazioni di fatturazione sono state aggiornate.
La tua fatturazione non è stata aggiornata.