El paquete flag de Go: cómo crear CLI sencillas con banderas tipadas
Introducción al paquete flag de Go para crear CLIs simples y efectivas
En el día a día de muchos desarrolladores surge la necesidad de crear pequeñas herramientas de línea de comandos que acepten opciones configurables. En Go el paquete estándar flag brinda una forma rápida y sin dependencias externas para definir banderas tipadas, generar ayuda automática y procesar los argumentos del programa. Este artículo está dirigido a quienes ya conocen los conceptos básicos de Go y desean aprender a construir CLI útiles en pocos minutos. Al finalizar sabrás cómo definir banderas de distintos tipos, usar valores por defecto, manejar errores, crear banderas personalizadas y organizar subcomandos con flag.NewFlagSet.
¿Qué ofrece el paquete flag?
El paquete flag forma parte de la biblioteca estándar de Go. Su responsabilidad principal es analizar la lista de argumentos que recibe el programa al iniciar y asignar los valores correspondientes a variables previamente declaradas. Cada bandera se asocia a una variable (o a un puntero) y posee un nombre, un valor por defecto y una breve descripción que se muestra en la ayuda. Las banderas pueden ser de tipos básicos como string, int, bool, float64 o duración (time.Duration). Además permite crear banderas de tipos propios mediante la interfaz flag.Value.
Una de las ventajas más destacadas es que, sin escribir casi nada de código extra, el paquete genera automáticamente la salida de ayuda cuando el usuario indica -h o -help o cuando ocurre un error de análisis. Esto reduce considerablemente el boilerplate típico de parsers de línea de comandos.
Importación y uso básico
Para comenzar basta con importar el paquete flag y declarar las variables que recibirán los valores. La función flag.Type (por ejemplo flag.String, flag.Int, flag.Bool) crea una bandera y devuelve un puntero a la variable donde se almacenará el resultado. Después de definir todas las banderas se llama a flag.Parse() que procesa os.Args y llena las variables. Finalmente se puede acceder a los valores mediante la desreferencia del puntero.
Ejemplo mínimo: un programa que saluda a un nombre proporcionado mediante una bandera string.
package main
import (
"flag"
"fmt"
)
func main() {
name := flag.String("nombre", "mundo", "Nombre a saludar
)
edad := flag.Int("edad", 0, "Edad de la persona (opcional)
)
flag.Parse()
fmt.Printf("Hola %s!
", *name)
if *edad > 0 {
fmt.Printf("Tenés %d años.
", *edad)
}
}Ejecutar el programa sin argumentos:
$ go run main.go
Hola mundo!Ejecutar con bandera -nombre y -edad:
$ go run main.go -nombre Ana -edad 25
Hola Ana!
Tenés 25 años.Ejecutar con -h para ver la ayuda:
$ go run main.go -h
Usage of main:
-edad int
Edad de la persona (opcional) (default 0)
-nombre string
Nombre a saludar (default "mundo" )Observá que la ayuda incluye automáticamente el valor por defecto entre paréntesis y la descripción que proporcionamos.
Definiendo banderas de distintos tipos
El paquete flag ofrece constructores para los tipos más usados. Cada uno recibe el nombre de la bandera, el valor por defecto y la descripción. A continuación se muestra un ejemplo que combina varios tipos.
package main
import (
"flag"
"fmt"
"time"
)
func main() {
server := flag.String("servidor", "localhost", "Dirección del servidor
puerto := flag.Int("puerto", 8080, "Puerto TCP del servidor
modo := flag.Bool("modo-debug", false, "Activar salida de depuración
tiempo := flag.Duration("timeout", 30*time.Second, "Tiempo máximo de espera
flag.Parse()
fmt.Printf("Servidor: %s
", *server)
fmt.Printf("Puerto: %d
", *puerto)
if *modo {
fmt.Printf("Modo debug activado
n }
fmt.Printf("Timeout: %v
", *tiempo)
}Salida de ejemplo:
$ go run main.go -servidor api.ejemplo.com -puerto 9000 -modo-debug -timeout 1m30s
Servidor: api.ejemplo.com
Puerto: 9000
Modo debug activado
Timeout: 1m30sNota cómo el flag Duration acepta unidades como s, m, h y se puede combinar (1m30s).
Valores por defecto y ayuda automática
Cada constructor permite especificar un valor que se usará cuando el usuario no provea la bandera. Ese mismo valor aparece en la ayuda. Si no se proporciona un valor por defecto, el tipo cero se usa (0 para numéricos, cadena vacía para string, false para bool). La función flag.PrintDefaults() imprime solo la lista de banderas con sus valores por defecto, útil cuando se quiere mostrar ayuda personalizada.
Ejemplo de uso de PrintDefaults dentro de una función de ayuda custom:
package main
import (
"flag"
"fmt"
"os"
)
func mostrarAyuda() {
fmt.Println("Uso: miapp [opciones]
t flag.PrintDefaults()
os.Exit(0)
}
func main() {
verbose := flag.Bool("v", false, "Modo verboso
flag.Usage = mostrarAyuda // reemplazamos la ayuda predeterminada
flag.Parse()
if *verbose {
fmt.Println("Modo verboso activado n }
}Al ejecutar go run main.go -h se mostrará la ayuda personalizada.
Accediendo a los valores después del parseo
Tras llamar a flag.Parse() las variables puntero contienen los valores finales. Es importante recordar que siempre se debe desreferenciar el puntero (*) para obtener el valor real. Si se intenta acceder antes de Parse() se obtendrá el valor por defecto o el cero según corresponda.
Un error común es olvidarse de pasar el puntero a funciones que esperan el valor directo. Por ejemplo, pasar name en vez de *name a fmt.Println producirá una dirección de memoria impresa, no el texto esperado.
Bandera booleana y forma corta
Las banderas booleanas se pueden activar simplemente indicando su nombre. También es posible usar una forma corta si se define un nombre de una sola rune. Por ejemplo:
package main
import (
"flag"
"fmt"
)
func main() {
forzar := flag.Bool("f", false, "Forzar operación sin confirmación
flag.Parse()
if *forzar {
fmt.Println("Operación forzada n }
}Ejecutar go run main.go -f imprime "Operación forzada" mientras que sin la bandera no muestra nada.
Bandera personalizada mediante flag.Value
Cuando se necesita un tipo que no está cubierto por los constructores estándar (por ejemplo una lista de strings, una IP o un enum) se puede crear una bandera personalizada implementando la interfaz flag.Value. Esta interfaz requiere dos métodos: Set(string) error y String() string.
Ejemplo: una bandera que acepta múltiples tags separados por coma y los guarda en un slice de strings.
package main
import (
"flag"
"fmt"
"strings"
)
type tagsFlag []string
func (t *tagsFlag) Set(val string) error {
*t = append(*t, strings.Split(val, \
DUGLAS MORENO
Comentarios (0)
Sé el primero en comentar.