Cómo usar el paquete Flag en Go

1 mar 2021 8 min di lettura
Cómo usar el paquete Flag en Go
Indice dei contenuti

Introducción

Las utilidades de la línea de comandos rara vez están listas para usarse sin una configuración adicional. En la mayoría de las plataformas, las utilidades de línea de comandos aceptan indicadores para personalizar la ejecución de los comandos. Los indicadores son cadenas delimitadas por valor-clave que se agregan después del nombre del comando. Go le permite crear utilidades de línea de comandos que aceptan banderas utilizando el flag de la biblioteca estándar.

En este tutorial, explorará varias formas de usar el flag para crear diferentes tipos de utilidades de línea de comando. Utilizará una bandera para controlar la salida del programa, introducirá argumentos posicionales en los que mezclará banderas y otros datos, y luego implementará comandos secundarios.

Use una bandera para cambiar el comportamiento de un programa

El uso del flag implica tres pasos: primero, defina las variables para capturar los valores de los indicadores, luego defina los indicadores que utilizará la aplicación Go y, finalmente, analizar los indicadores proporcionados a la aplicación en tiempo de ejecución. La mayoría de las funciones dentro del flag implican definir banderas y vincularse a variables definidas por el usuario. La fase de análisis es manejada por la función Parse().

Creará un programa que define una bandera booleana que modifica el mensaje que se imprimirá en la salida estándar. Si hay una -color, el programa imprimirá un mensaje en azul. Si no se da ninguna bandera, el mensaje se imprimirá sin ningún color.

Cree un nuevo archivo llamado boolean.go:

nano boolean.go

Agregue el siguiente código al archivo para crear el programa:

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!")
 }
boolean.go

Este ejemplo utiliza secuencias de escape ANSI para indicar al terminal que muestre la salida en color. Estas son secuencias especializadas de caracteres, por lo que tiene sentido definir un nuevo tipo para ellos. En este ejemplo, llamamos a ese tipo (tipo) Color y definimos el tipo (tipo) como una string. Luego definimos una tabla de colores (paleta) para usar en el const que sigue. La colorize definida después del const acepta una de estas constantes de Color string para colorear el mensaje. Luego le indica al terminal que cambie de color imprimiendo primero la secuencia de escape para el color solicitado, luego imprime el mensaje y finalmente solicita al terminal que restaure su color imprimiendo la secuencia especial de restauración del color.

Dentro de main, usamos la flag.Bool para definir una bandera booleana llamada color. El segundo parámetro de esta función, false, establece el valor predeterminado para esta bandera cuando no se proporciona. Contrariamente a las expectativas que pueda tener, establecer esto en true no revierte el comportamiento de tal manera que proporcionar una bandera hará que se vuelva falso. En consecuencia, el valor de este parámetro es casi siempre false con banderas booleanas.

El parámetro final es una cadena de documentación que se puede imprimir como un mensaje de uso. El valor devuelto por esta función es un puntero bool La flag.Parse en la siguiente línea usa este puntero para establecer la variable base bool en las banderas pasadas por el usuario. Entonces podemos controlar el valor de este bool desreferenciando el puntero. Usando este valor booleano, podemos llamar a colorize cuando la -color está activada y llamar a la fmt.Println cuando la bandera está ausente.

Guarde el archivo y ejecute el programa sin la bandera:

go run boolean.go

Verá el siguiente resultado:

Hello, Noviello!

Ahora ejecute este programa nuevamente con la bandera -color

go run boolean.go -color

La salida será el mismo texto, pero esta vez en color azul.

Las banderas no son los únicos valores que se pasan a los comandos. También puede enviar nombres de archivos u otros datos.

Trabajar con argumentos posicionales

Los comandos generalmente aceptan una serie de argumentos que sirven como foco del comando. Por ejemplo, el head, que imprime las primeras líneas de un archivo, a menudo se invoca como head example.txt. El example.txt es un argumento posicional en la invocación del comando head

La función Parse() continuará analizando los indicadores que encuentre hasta que encuentre un argumento que no sea un indicador. El flag los hace disponibles a través de las Args() y Arg().

Para ilustrar esto, creará una reimplementación simplificada del head, que muestra las primeras líneas de un archivo determinado:

Cree un nuevo archivo llamado head.go y agregue el siguiente código:

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)
 }
 }
head.go

Primero, definimos un count variables para contener el número de líneas que el programa debe leer del archivo. Luego definimos la bandera -n flag.IntVar, reflejando el comportamiento del programa head Esta función nos permite pasar nuestro puntero a una variable a diferencia de las flag que no tienen el sufijo Var Aparte de esta diferencia, el resto de parámetros seguirán flag.IntVar su contraparte flag.Int: el nombre de la bandera, un valor por defecto y una descripción. Como en el ejemplo anterior, luego llamamos a flag.Parse() para procesar la entrada del usuario.

La siguiente sección lee el archivo. Primero definimos una variable io.Reader que se establecerá en el archivo solicitado por el usuario o en la entrada estándar pasada al programa. Dentro de la if, usamos la flag.Arg para acceder al primer argumento posicional después de todas las banderas. Si el usuario ha proporcionado un nombre de archivo, se establecerá. De lo contrario, será la cadena vacía ( "" ). Cuando hay un nombre de archivo, usamos la os.Open para abrir ese archivo y configurar el io.Reader en ese archivo. De lo contrario, usamos os.Stdin para leer desde la entrada estándar.

La sección final usa un *bufio.Scanner creado con bufio.NewScanner para leer las io.Reader de la variable in Examinamos hasta el valor de count for, llamando a break si el buf.Scan línea buf.Scan produce un false, lo que indica que el número de líneas es menor que el número solicitado por el usuario.

Ejecute este programa y muestre el contenido del archivo que acaba de escribir usando head.go como argumento del archivo:

go run head.go -- head.go

El separador -- es una bandera especial reconocida por el flag que indica que no siguen más argumentos de bandera. Cuando ejecuta este comando, obtiene el siguiente resultado:

package main

 import (
 "bufio"
 "flag"

Utilice el -n que definió para ajustar la cantidad de salida:

go run head.go -n 1 head.go

Esto solo produce la declaración del paquete:

package main

Finalmente, cuando el programa detecta que no se proporcionaron argumentos posicionales, lee la entrada de la entrada estándar, al igual que head. Intente ejecutar este comando:

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

Verá la salida:

fish
 lobsters
 sharks

El comportamiento de las flag que ha visto hasta ahora se ha limitado a examinar la invocación completa del comando. No siempre desea este comportamiento, especialmente si está escribiendo una herramienta de línea de comandos que admita comandos secundarios.

Utilice FlagSet para implementar comandos secundarios

Las aplicaciones modernas de línea de comandos a menudo implementan "subcomandos" para agrupar un conjunto de herramientas en un solo comando. La herramienta más conocida que utiliza este modelo es git. Al mirar un comando como git init, git es el comando e init es el subcomando de git. Una característica notable de los subcomandos es que cada subcomando puede tener su propia colección de indicadores.

Las aplicaciones Go pueden admitir comandos secundarios con su propio conjunto de indicadores utilizando el flag.(*FlagSet). Para ilustrar esto, cree un programa que implemente un comando usando dos subcomandos con diferentes banderas.

Cree un nuevo archivo llamado subcommand.go y agregue el siguiente contenido al archivo:

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)
 }
 }
subcommand.go

Este programa está dividido en varias partes: la main, la root y las funciones individuales para implementar el comando secundario. La main maneja los errores devueltos por los comandos. Si una función devuelve un error, la if, imprime el error y el programa sale con un código de estado de 1, lo que indica que se ha producido un error en el resto del sistema operativo. Dentro de main, pasamos todos los argumentos con los que se invocó el programa root Eliminamos el primer argumento, que es el nombre del programa (en los ejemplos anteriores ./subcommand ) cortando primero os.Args

La función root []Runner donde se definirán todos los subcomandos. Runner es una interfaz de subcomando que permite a la root recuperar el subcommand Name() y compararlo con el contenido de la variable del subcomando. Una vez que encontramos el cmds correcto después de repetir la variable cmds, inicializamos el subcomando con el resto de los argumentos e invocamos el Run()

Definimos solo un subcomando, aunque este marco nos permite crear fácilmente otros. Con GreetCommand creamos una instancia usando NewGreetCommand en la que creamos un nuevo *flag.FlagSet usando flag.NewFlagSet. flag.NewFlagSet toma dos argumentos: un nombre para el conjunto de banderas y una estrategia para informar errores de análisis. Se puede acceder al *flag.FlagSet flag.(*FlagSet).Name. Usamos esto en el (*GreetCommand).Name() para que el nombre del subcomando coincida con el nombre que le dimos *flag.FlagSet. NewGreetCommand también define una bandera -name similar a los ejemplos anteriores, pero en su lugar requiere esto como una fuera de rango *flag.FlagSet de *GreetCommand, gc.fs Cuando root llama al método Init() *GreetCommand, pasamos los argumentos proporcionados al método Parse del campo *flag.FlagSet

Será más fácil ver los subcomandos si crea este programa y luego lo ejecuta. Construye el programa:

go build subcommand.go

Ahora ejecute el programa sin argumentos:

./subcommand

Verá esta salida:

You must pass a sub-command

Ahora ejecute el programa con el comando secundario greet:

./subcommand greet

Esto produce la siguiente salida:

Hello World!

Ahora use la bandera -name greet para especificar un nombre:

./subcommand greet -name Noviello

Verá esta salida del programa:

Hello Noviello!

Este ejemplo ilustra algunos principios detrás de cómo se pueden estructurar las aplicaciones de línea de comando más grandes en Go. Los conjuntos de FlagSet están diseñados para brindar a los desarrolladores más control sobre dónde y cómo se procesan los indicadores mediante la lógica de análisis de indicadores.

Conclusión

Los indicadores hacen que sus aplicaciones sean más útiles en múltiples contextos porque les dan a sus usuarios control sobre cómo se ejecutan los programas. Es importante proporcionar a los usuarios una configuración predeterminada útil, pero debe darles la oportunidad de anular cualquier configuración que no funcione para su situación. Ha visto que el flag ofrece opciones flexibles para presentar opciones de configuración a sus usuarios. Puede elegir algunas banderas simples o crear un conjunto extensible de subcomandos.

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.