Saltar al contenido principal
eLearner.app
Módulo 10 · Lección 4 de 549/50 en el curso~18 min
Lecciones del módulo (4/5)

Mini-proyecto: una CLI con banderas

Juntemos los componentes básicos de los módulos anteriores para crear una pequeña CLI idiomática: análisis de indicadores con el paquete flag, manejo de argumentos posicionales, errores en os.Stderr y el código de salida correcto.

El paquete flag

Go
package main

import (
    "flag"
    "fmt"
    "os"
)

func main() {
    n := flag.Int("n", 1, "number of repetitions")
    upper := flag.Bool("u", false, "print in uppercase")
    flag.Parse()

    args := flag.Args() // remaining positional arguments
    if len(args) < 1 {
        fmt.Fprintln(os.Stderr, "usage: echo [-n N] [-u] <text>")
        os.Exit(2)
    }

    text := args[0]
    if *upper {
        text = strings.ToUpper(text)
    }
    for i := 0; i < *n; i++ {
        fmt.Println(text)
    }
}

Puntos clave:

  • flag.Int, flag.String, flag.Bool, flag.Duration (etc.) devuelven un puntero: usas *n, *upper.
  • flag.Parse() debe llamarse UNA VEZ, antes de leer los valores y antes de flag.Args().
  • flag.Args() devuelve los argumentos posicionales (aquellos después de -- o después del último indicador).
  • Formularios aceptados: flag.String0, flag.String1, flag.String2. Booleanos: flag.String3 (verdadero), flag.String4.

Códigos de salida convencionales

Go
os.Exit(0) // success
os.Exit(1) // generic error
os.Exit(2) // usage error (incorrect flag usage)

Los shells y los scripts esperan estos códigos: utilícelos de forma coherente. Nunca salga de una rutina no principal con os.Exit: omite cada defer, incluido el vaciado de log.

Stderr frente a Stdout

Antigua y sagrada regla de Unix:

  • os.Stdout → salida útil, canalizable (cli | grep ...).
  • os.Stderr → diagnóstico, errores, barras de progreso, uso.
Go
fmt.Println("result: 42")                   // stdout
fmt.Fprintln(os.Stderr, "error: ...")       // stderr

De esta manera cli 2>/dev/null muestra solo los resultados, cli >out.txt redirige solo el resultado y los errores permanecen visibles.

CÓDIGOPH0 frente a CÓDIGOPH1

  • fmt para salida del usuario.
  • log para diagnóstico: agrega una marca de tiempo, escribe en os.Stderr de forma predeterminada, tiene log.Fatal (registros + os.Exit(1)) y log.Panic.
Go
log.SetFlags(log.LstdFlags | log.Lshortfile) // timestamp + file:line
log.Fatal("boom") // prints and exits with 1

Para aplicaciones modernas (Go 1.21+), use log/slog para el registro estructurado (JSON o texto, niveles, campos escritos).

Arquitectura: separar main de la lógica

Go
func main() {
    if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

func run(args []string, stdout, stderr io.Writer) error {
    // here use a new flag.FlagSet instead of the global flag.CommandLine
    fs := flag.NewFlagSet("echo", flag.ContinueOnError)
    fs.SetOutput(stderr)
    n := fs.Int("n", 1, "repetitions")
    if err := fs.Parse(args); err != nil {
        return err
    }
    // ...
    return nil
}

Ventajas:

  • Comprobable: pasa bytes.Buffer como stdout/stderr y verifica la salida.
  • Sin estado global: cada FlagSet está aislado.
  • main se queda pequeño: prepara IO + delegados.

Subcomandos

Para CLI con subcomandos (mycli serve, mycli migrate), un esquema sin dependencias externas:

Go
func main() {
    if len(os.Args) < 2 {
        usage()
        os.Exit(2)
    }
    switch os.Args[1] {
    case "serve":
        serveCmd(os.Args[2:])
    case "migrate":
        migrateCmd(os.Args[2:])
    default:
        usage()
        os.Exit(2)
    }
}

Cada subcomando tiene su propio flag.NewFlagSet. Para proyectos más grandes, bibliotecas como cobra o urfave/cli estructuran este esquema con ayuda generada automáticamente.

Ejercicios

Ejercicio#go.m10.l4.e1
Intentos: 0Cargando...

Definimos flag -n de tipo int con valor predeterminado 1 y describimos 'ripetizioni', luego llamamos flag.Parse() y marcamos el valor.

Cargando editor...

Solución disponible después de 3 intentos

Ejercicio#go.m10.l4.e2
Intentos: 0Cargando...

Si no hay ningún argumento posicional detrás de flag.Parse, estampa 'manca argumento' en Stderr y termina con os.Exit(1).

Cargando editor...

Solución disponible después de 3 intentos

Cuestionario#go.m10.l4.e3
Listo

¿Qué mensajes de error y uso transmiten en una CLI compatible con Unix?

Go
fmt.Fprintln(???, "uso: prog ...")
Opciones de respuesta