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:
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:
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:
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.