os.Args
os.Args es un slice de strings. os.Args[0] es el nombre del programa y el resto son los argumentos tal como los pasó el shell.
En el panel Args del editor, cada campo es un argumento y se pasa tal cual. Prueba hello, two words y -v en tres campos: el programa ve tres argumentos, y two words sigue siendo un solo argumento con un espacio dentro. En una terminal es el shell quien separa, así que lo mismo sería go run . hello "two words" -v.
Comprueba siempre len(os.Args) antes de indexar. os.Args[1] sin argumentos provoca un panic con index out of range [1] with length 1.
Los argumentos son strings. Convierte los números con strconv.Atoi o strconv.ParseFloat y maneja el error, porque los usuarios escriben cualquier cosa.
El paquete flag
Para opciones como -port 8080 -verbose, usa flag. Analiza, convierte tipos, informa de errores y genera un mensaje de ayuda.
Sin argumentos imprime hello, world una vez. En el panel Args, prueba -name y Gopher en dos campos, y luego -count=3, -loud y extra en tres más. El programa imprime HELLO, GOPHER! tres veces y remaining args: [extra].
Cómo funcionan los flags:
- Cada función de definición (
flag.String,flag.Int,flag.Bool,flag.Float64,flag.Duration,flag.Uint64...) recibe un nombre, un valor por defecto y un texto de ayuda, y devuelve un puntero. Lee el valor con*namedespués deflag.Parse(). - Las variantes
Varse enlazan a una variable que ya tienes:flag.IntVar(&cfg.Port, "port", 8080, "port"). Queda más ordenado con un struct de configuración. - Los usuarios pueden escribir
-name value,-name=value,--name valueo--name=value. Go no distingue entre uno y dos guiones. - Los booleanos necesitan
=para recibir un valor.-loudpone true,-loud=falsepone false, pero-loud falsepone true y dejafalsecomo argumento posicional. - El análisis se detiene en el primer argumento que no es un flag (o en
--).prog file.txt -vtrata-vcomo argumento posicional. Pon los flags primero. flag.Args()devuelve los argumentos posicionales que quedan,flag.NArg()cuántos son yflag.Arg(i)uno de ellos.
Un flag desconocido o un valor incorrecto imprime un error más la ayuda, y sale con estado 2. -h o -help imprime la ayuda y sale con estado 0 (desde Go 1.15). El texto de ayuda se genera a partir de tus definiciones:
Usage of greet:
-count int
how many times (default 1)
-delay duration
pause between greetings, e.g. 10ms
-loud
shout the greeting
-name string
who to greet (default "world")
Asigna una función a flag.Usage para imprimir tu propia cabecera antes de llamar a flag.PrintDefaults().
Subcomandos con FlagSet
Herramientas como git commit -m msg tienen subcomandos con sus propios flags. Crea un flag.FlagSet por subcomando y elige uno con un switch sobre el primer argumento:
flag.ContinueOnError hace que Parse devuelva un error en lugar de terminar el programa, lo que mantiene la función testeable. run recibe los argumentos como parámetro en vez de leer os.Args, así que un test puede llamar directamente a run([]string{"list", "-all"}). Prueba list y -all en el panel Args, o delete para ver la ruta de error.
Para CLIs grandes con comandos anidados, autocompletado en el shell y documentación generada, la mayoría de proyectos usan el paquete de terceros github.com/spf13/cobra. El paquete estándar flag cubre bien las herramientas pequeñas.
Variables de entorno
os.Getenvdevuelve""tanto si la variable no existe como si está definida como cadena vacía.os.LookupEnvlos distingue.- Los valores siempre son strings. Conviértelos y valídalos al arrancar, y falla con un mensaje claro en lugar de a mitad de una petición.
os.Setenvafecta al proceso actual y a los procesos hijos lanzados después. No puede cambiar el entorno del shell que te lanzó.os.Environ()devuelve todas las variables como strings"KEY=value".
Una organización habitual de la configuración: flags para lo que escribe una persona, variables de entorno para los ajustes de despliegue (las plataformas de contenedores las definen), con los flags por encima del entorno y el entorno por encima de los valores por defecto.
Códigos de salida y os.Exit
Un programa Go sale con estado 0 cuando main retorna. os.Exit(code) termina el proceso de inmediato con ese estado. Por convención, 0 es éxito, 1 un error general y 2 un error de uso (el paquete flag usa 2).
os.Exit no ejecuta las funciones diferidas. Los archivos no se vuelcan y la limpieza con defer se salta. log.Fatal llama a os.Exit(1) y tiene el mismo efecto. Deja os.Exit en un solo sitio, al final de main, y pon el programa real en una función run que devuelva un error, como hace el ejemplo de subcomandos:
func main() {
if err := run(os.Args[1:]); err != nil {
fmt.Fprintln(os.Stderr, "error:", err)
os.Exit(1)
}
}
Escribe los mensajes de error en os.Stderr, no en os.Stdout, para que sigan visibles cuando la salida se redirige a un archivo o se pasa por una tubería a otro comando. Un panic que no se recupera sale con estado 2.
Errores comunes
- Indexar
os.Argssin comprobar su longitud. Los argumentos que faltan provocan un panic. - Leer flags antes de
flag.Parse(). Obtienes los valores por defecto. - Olvidar el
*.fmt.Println(port)imprime una dirección como0xc000012345, no el valor. - Poner flags después de argumentos posicionales.
prog input.txt -vno analiza-v. -verbose falseen un flag booleano. Escribe-verbose=false.- Llamar a
os.Exitolog.Fatalen lo profundo del programa. La limpieza diferida nunca se ejecuta y el código no se puede testear. Devuelve los errores hastamain.
Preguntas frecuentes
¿Cómo obtengo los argumentos de línea de comandos en Go?
os.Args es un []string con el nombre del programa en el índice 0 y los argumentos después. os.Args[1:] son los argumentos que escribió el usuario. Comprueba len(os.Args) antes de indexar, o el programa provocará un panic cuando falte un argumento.
¿Cómo se usa el paquete flag en Go?
Declara los flags, llama a flag.Parse() y luego léelos: port := flag.Int("port", 8080, "port to listen on"), flag.Parse(), fmt.Println(*port). Las funciones devuelven punteros. Los usuarios escriben -port=9000, -port 9000 o --port 9000, y -h imprime la ayuda generada.
¿Cómo leo una variable de entorno en Go?
os.Getenv("HOME") devuelve el valor, o una cadena vacía si la variable no está definida. Para distinguir entre no definida y definida como vacía, usa v, ok := os.LookupEnv("HOME"). os.Setenv cambia el entorno del proceso actual y de los procesos hijos que lance después.
¿os.Exit ejecuta las funciones diferidas en Go?
No. os.Exit termina el proceso de inmediato con el código de estado indicado, y las llamadas diferidas no se ejecutan, así que puede perderse salida en buffer y los archivos pueden quedar sin volcar. Un patrón habitual es func main() { if err := run(); err != nil { fmt.Fprintln(os.Stderr, err); os.Exit(1) } }, con todo el trabajo real y los defers dentro de run.