Введение
Утилиты командной строки редко готовы к использованию без дополнительной настройки. На большинстве платформ утилиты командной строки принимают флаги для настройки выполнения команд. Флаги — это строки, разделенные ключом и значением, которые добавляются после имени команды. Go позволяет создавать утилиты командной строки, которые принимают флаги, используя пакет flag
из стандартной библиотеки.
В этом руководстве вы изучите различные способы использования пакета flag
для создания различных типов утилит командной строки. Вы будете использовать флаг для управления выводом программы, вводя позиционные аргументы, в которые вы смешиваете флаги и другие данные, а затем реализуете вторичные команды.
Используйте флаг, чтобы изменить поведение программы
Использование пакета flag
включает три шага: сначала определить переменные для захвата значений флагов, затем определить флаги, которые будут использоваться приложением Go, и, наконец, проанализировать флаги, предоставляемые приложению во время выполнения. Большинство функций в пакете flag
включают определение флагов и привязку к определяемым пользователем переменным. Этап синтаксического анализа обрабатывается функцией Parse()
.
Вы создадите программу, определяющую логический флаг, изменяющий сообщение, которое будет напечатано на стандартном выводе. Если есть флаг -color
, программа напечатает сообщение синим цветом. Если флаг не указан, сообщение будет напечатано без цвета.
Создайте новый файл с именем boolean.go
:
nano boolean.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
и добавьте следующий код:
Во-первых, мы определяем переменную 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
и добавьте в него следующее содержимое:
Эта программа разделена на несколько частей: 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
предлагает гибкие возможности для представления параметров конфигурации вашим пользователям. Вы можете выбрать несколько простых флагов или создать расширяемый набор подкоманд.