Los servicios de línea de comandos rara vez son útiles cuando vienen listos para usar sin configuración adicional. Es importante disponer de buenos valores predeterminados, pero las utilidades prácticas deben aceptar la configuración de parte de los usuarios. En la mayoría de las plataformas, los servicios de línea de comandos aceptan indicadores para personalizar la ejecución del comando. Los parámetros son cadenas delimitadas por el valor de clave agregadas después del nombre del comando. Go le permite crear servicios de línea de comandos que acepten indicadores usando el paquete flag
de la biblioteca estándar.
En este tutorial, explorará varias formas de usar el paquete flag
para crear diferentes tipos de utilidades de línea de comandos. Utilizará un indicador para controlar el resultado del programa, introducir argumentos en posición en los que intercambie indicadores y otros datos, y luego implementará subcomandos.
Al usar el paquete flag
se deben seguir tres pasos: primero, definir variables para capturar valores de indicadores, definir los parámetros que utilizará su aplicación de Go y, por último, analizar los parámetros proporcionados a la aplicación al ejecutarse. La mayor parte de las funciones del paquete flag
se ocupan de definir los indicadores y vincularlos a las variables que definió. La función Parse()
lleva a cabo la fase de análisis.
Para ilustrar esto, creará un programa que defina un indicador booleano que cambie el mensaje que se imprimirá para un resultado estándar. Si se proporciona un indicador -color
, el programa imprimirá un mensaje en azul. Si no se proporciona un indicador, el mensaje se imprimirá sin color.
Cree un nuevo archivo llamado boolean.go:
- nano boolean.go
Añada 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, DigitalOcean!")
return
}
fmt.Println("Hello, DigitalOcean!")
}
En este ejemplo, se utilizan secuencias de escape ANSI para dar instrucciones al terminal a fin de que muestre un resultado colorido. Estas son secuencias especializadas de caracteres, por lo que tiene sentido definir un nuevo tipo para ellos. En este ejemplo, asignamos el nombre Color
al tipo y lo definimos como una string
. Luego, definimos una paleta de colores para usar en el bloque const
que sigue. La función colorize
definida después del bloque const
acepta una de estas constantes Color
y una variable string
para que el mensaje sea de color. Luego, indica al terminal que cambie de color imprimiendo primero la secuencia de escape para el color solicitado, luego imprime el mensaje y finalmente solicita que el terminal restablezca su color imprimiendo la secuencia especial de restablecimiento de color.
En main
, usamos la función flag.Bool paradefinir
un indicador booleano llamado color
. El segundo parámetro de esta función, false
, establece el valor predeterminado para este indicador cuando no se proporciona. En contra de las expectativas que pueda tener, fijar esto en true
no invierte el comportamiento de modo tal que la incorporación de un indicador haga que se convierta en falso. Por lo tanto, el valor de este parámetro es casi siempre false
con indicadores de booleano.
El parámetro final es una cadena de documentación que puede imprimirse como mensaje de uso. El valor devuelto de esta función es un puntero de un bool
. La función flag.Parse
en la siguiente línea utiliza este puntero para configurar la variable bool
en función de los indicadores pasados por el usuario. Luego, podemos verificar el valor de este puntero bool
eliminado su referencia. Se puede encontrar más información sobre las variables de punteros en el tutorial sobre punteros. Mediante este valor booleano, podemos invocar colorize
cuando esté configurado el puntero -color
e invocar la variable fmt.Println
cuando no se encuentre el indicador.
Guarde el archivo y ejecute el programa sin indicadores:
- go run boolean.go
Verá el siguiente resultado:
OutputHello, DigitalOcean!
Ahora, ejecute este programa de nuevo con el indicador color
:
- go run boolean.go -color
El resultado será el mismo texto, pero esta vez en color azul.
Los indicadores no son los únicos valores pasados a comandos. Es posible que también envíe nombres de archivo u otros datos.
Normalmente, los comandos toman una serie de argumentos que actúan como sujeto del foco del comando. Por ejemplo, el comando head
, que imprime las primeras líneas de un archivo, a menudo se invoca como head example.txt
. El archivo example.txt
es un argumento posicional en la invocación del comando head
.
La función Parse()
continuará analizando los indicadores que encuentre hasta detectar un argumento sin indicador. El paquete flag
los pone a disposición a través de las funciones Args ()
y Arg ()
.
Para ilustrar esto, compilará una nueva implementación simplificada del comando head
, que muestra las primeras líneas de un archivo dado:
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)
}
}
Primero, definimos una variable count
para contener el número de líneas que el programa debería leer del archivo. Luego definimos el indicador -n
usando flag.IntVar
, para reflejar el comportamiento del programa head
original. Esta función nos permite pasar nuestro propio puntero a una variable en contraste con las funciones flag
que no tienen el sufijo Var
. Además de esta diferencia, el resto de los parámetros de flag.IntVar
siguen a su homólogo flag.Int
: el nombre del indicador, un valor predeterminado y una descripción. Como en el ejemplo anterior, invocamos 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 la entrada estándar pasada al programa. Dentro de la instrucción if
, usamos la función flag.Arg
para acceder al primer argumento posicional después de todos los indicadores. Si el usuario proporcionó un nombre de archivo, se establecerá. De lo contrario, será la cadena vacía (""
). Cuando hay un nombre de archivo presente, usamos la función os.Open
para abrir ese archivo y configurar el io.Reader
que definimos antes para él. De lo contrario, usamos os.Stdin
para la lectura desde entrada estándares.
En la sección final se utiliza un * bufio.Scanner
creado con bufio.NewScanner
para leer líneas de la variable in
de io.Reader
. Realizamos iteraciones hasta el valor de count
utilizando un bucle for
, invocando break
si del análisis de la línea con buf.Scan
surge un valor false
, lo cual 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 un indicador especial reconocido por el paquete flag
que señala que no hay más argumentos de indicador a continuación. Cuando ejecute este comando, verá el siguiente resultado:
Outputpackage main
import (
"bufio"
"flag"
Utilice el indicador -n
que definió para ajustar la cantidad de resultado:
- go run head.go -n 1 head.go
Como resultado solo se muestra la instrucción del paquete:
Outputpackage main
Por último, cuando el programa detecta que no se proporcionaron argumentos posicionales, lee la entrada estándar como en el caso de head
. Intente ejecutar este comando:
- echo "fish\nlobsters\nsharks\nminnows" | go run head.go -n 3
Verá el resultado:
Outputfish
lobsters
sharks
El comportamiento de las funciones flag
que vio hasta ahora se limitó a examinar toda la invocación del comando. No siempre le convendrá este comportamiento, sobre todo si escribe una herramienta de línea de comandos que admite subcomandos.
Las aplicaciones de línea de comandos modernas a menudo implementan “subcomandos” para empaquetar una serie de herramientas bajo un único comando. La herramienta más conocida que utiliza este patrón es git
. Si se examina 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 uno puede tener su propio conjunto de indicadores.
Las aplicaciones de Go pueden admitir subcomandos con su propio conjunto de indicadores usando el tipo flag.( *FlagSet)
. Para ilustrar esto, cree un programa que ejecute un comando usando dos subcomandos con diferentes indicadores.
Cree un nuevo archivo llamado subcommand.go
y agregue a este el siguiente contenido:
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)
}
}
Este programa se divide en algunas partes: la función main
, la función root
y las funciones individuales para implementar el subcomando. La función main
maneja los errores que muestran los comandos. Si alguna función muestra un error, la instrucción if
lo detectará y lo imprimirá, y el programa se cerrará con un código de estado 1
, lo cual indica que se produjo un error en el resto del sistema operativo. Dentro de main
, pasamos todos los argumentos con los que se invocó el programa a root
. Eliminamos el primer argumento, que es el nombre del programa (en los ejemplos anteriores, ./subcommand
) cortando os.Args
primero.
La función root
define [] Runner
, donde se definirían todos los subcomandos. Runner
es una interfaz para subcomandos que permite a root
recuperar el nombre del subcomando usando Name ()
y compararlo con la variable subcommand
de contenido. Una vez que se encuentre el subcomando correcto después de la iteración de la variable cmds
, inicializamos el subcomando con el resto de los argumentos e invocamos el método Run()
de ese comando.
Solo definimos un subcomando, aunque este marco nos permitiría crear fácilmente otros. Se crean instancias de GreetCommand
usando NewGreetCommand
donde creamos un nuevo *flag.FlagSet
usando flag.NewFlagSet
. flag.NewFlagSet
toma dos argumentos: un nombre para el conjunto de indicadores y una estrategia para informar errores de análisis. Es posible acceder al nombre de *flag.FlagSet
con el método flag.( *FlagSet. Name
. Lo usamos en (*GreetCommand)
. El método Name() para que el nombre del subcomando coincida con el nombre que asignamos a *flag.FlagSet
. NewGreetCommand
también define un indicador -name
de una manera similar a la de los ejemplos anteriores, pero como alternativa lo invoca como un método fuera del campo *flag.FlagSet
del *GreetCommand
, gc.fs
. Cuando root
invoca al método Init ()
del * GreetCommand
, pasamos los argumentos proporcionados al método Parse
del campo *flag.FlagSet
.
Será más sencillo ver subcomandos si compila este programa y lo ejecuta. Compile el programa:
- go build subcommand.go
Ahora, ejecute el programa sin argumentos:
- ./subcommand
Verá este resultado:
OutputYou must pass a sub-command
Ahora, ejecute el programa con el subcomando greet
:
- ./subcommand greet
Esto produce el siguiente resultado:
OutputHello World !
Ahora, utilice el indicador -name
con greet
para especificar un nombre:
- ./subcommand greet -name Sammy
Verá este resultado del programa:
OutputHello Sammy !
Este ejemplo permite ver algunos principios relacionados con la estructura que podrían tener las aplicaciones de línea de comandos más grandes en Go. Los FlagSets
están diseñados para dar a los desarrolladores más control respecto de dónde y cómo lógica de análisis de indicadores los procesa.
Los indicadores hacen que sus aplicaciones sean más útiles en más contextos porque dan a sus usuarios control respecto de cómo se ejecutan los programas. Es importante proporcionar a los usuarios valores predeterminados útiles, pero debería darles la oportunidad de anular ajustes que no funcionen en sus casos. Pudo observar que el paquete flag
ofrece alternativas flexibles para presentar opciones de configuración a sus usuarios. Puede elegir algunos indicadores sencillos o crear una serie de subcomandos extensible. En cualquiera de los casos, usar el paquete flag
le permitirá crear utilidades siguiendo el estilo de la larga historia de herramientas de línea de comandos flexibles y programables.
Para obtener más información sobre el lenguaje de programación de Go, consulte nuestra serie Cómo realizar codificaciones en Go.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!