Menu

Argumentos de línea de comandos en Golang: os.Args, flag y env

Cómo lee un programa Go su línea de comandos: os.Args, el paquete flag para opciones tipadas, subcomandos con FlagSet, variables de entorno con os.Getenv y os.LookupEnv, y códigos de salida con os.Exit.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

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 *name después de flag.Parse().
  • Las variantes Var se 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 value o --name=value. Go no distingue entre uno y dos guiones.
  • Los booleanos necesitan = para recibir un valor. -loud pone true, -loud=false pone false, pero -loud false pone true y deja false como argumento posicional.
  • El análisis se detiene en el primer argumento que no es un flag (o en --). prog file.txt -v trata -v como argumento posicional. Pon los flags primero.
  • flag.Args() devuelve los argumentos posicionales que quedan, flag.NArg() cuántos son y flag.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.Getenv devuelve "" tanto si la variable no existe como si está definida como cadena vacía. os.LookupEnv los 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.Setenv afecta 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.Args sin 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 como 0xc000012345, no el valor.
  • Poner flags después de argumentos posicionales. prog input.txt -v no analiza -v.
  • -verbose false en un flag booleano. Escribe -verbose=false.
  • Llamar a os.Exit o log.Fatal en lo profundo del programa. La limpieza diferida nunca se ejecuta y el código no se puede testear. Devuelve los errores hasta main.

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.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR