¿Por qué Go es el lenguaje ideal para utilidades CLI?
\n\nImagínese: necesita escribir una herramienta que se ejecute en la consola, procese archivos, acceda a API o simplemente automatice tareas rutinarias. Puede elegir Python, Node.js o Bash. Pero si la velocidad, el tamaño del binario y la facilidad de distribución son críticos, su elección son las utilidades CLI en Go.
\n\nGo se compila en un único binario estático. Sin dependencias, sin "¿tienes Python 3.8 instalado?". Descárgalo y ejecútalo. Esto hace que las herramientas Golang CLI sean el estándar de facto en el mundo DevOps (recuerde Docker, Kubernetes, Hugo; todos están escritos en Go).
\n\nEn este artículo no solo hablaremos de teoría. Crearemos desde cero una utilidad de consola en Go real que analizará JSON, trabajará con HTTP y mostrará tablas bonitas. Aprenderá cómo estructurar un proyecto, manejar errores y compilar para cualquier plataforma.
\n\nConfiguración del entorno y estructura del proyecto
\n\nAntes de escribir código, asegúrese de tener Go instalado (versión 1.21+). Verificarlo es sencillo:
\n\ngo version\n\nCreemos un directorio para nuestro proyecto. Llamaremos a la utilidad json2table; leerá JSON desde stdin o un archivo y mostrará los datos en forma de tabla.
mkdir json2table\ncd json2table\ngo mod init github.com/su-usuario/json2table\n\nLa estructura del proyecto será clásica para el desarrollo en Go:
\n\n- \n
main.go— punto de entrada \n internal/— lógica interna (análisis, salida) \n cmd/— código para manejar comandos (si usamos Cobra) \n
Comenzaremos de forma simple, sin frameworks, y luego añadiremos banderas y ayuda.
\n\nEscribimos el núcleo de la utilidad: análisis de JSON y salida de tabla
\n\nCree el archivo internal/parser.go. Aquí analizaremos JSON y lo convertiremos en una estructura.
package internal\n\nimport (\n "encoding/json"\n "fmt"\n "io"\n)\n\n// Record representa una fila de la tabla\ntype Record map[string]interface{}\n\n// ParseJSON lee JSON desde un reader y devuelve un slice de registros\nfunc ParseJSON(r io.Reader) ([]Record, error) {\n var data []Record\n decoder := json.NewDecoder(r)\n if err := decoder.Decode(&data); err != nil {\n return nil, fmt.Errorf("error al analizar JSON: %w", err)\n }\n return data, nil\n}\n\nAhora crearemos internal/table.go para la salida formateada:
package internal\n\nimport (\n "fmt"\n "strings"\n "text/tabwriter"\n "os"\n)\n\n// PrintTable muestra los registros en forma de tabla con alineación\nfunc PrintTable(records []Record) {\n if len(records) == 0 {\n fmt.Println("No hay datos para mostrar")\n return\n }\n\n // Recopilamos todas las claves únicas (encabezados de columna)\n headers := make([]string, 0)\n seen := make(map[string]bool)\n for _, rec := range records {\n for key := range rec {\n if !seen[key] {\n headers = append(headers, key)\n seen[key] = true\n }\n }\n }\n\n // Usamos tabwriter para una salida bonita\n w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)\n\n // Encabezado\n fmt.Fprintln(w, strings.Join(headers, "\\t"))\n\n // Separador\n sep := make([]string, len(headers))\n for i, h := range headers {\n sep[i] = strings.Repeat("-", len(h))\n }\n fmt.Fprintln(w, strings.Join(sep, "\\t"))\n\n // Datos\n for _, rec := range records {\n row := make([]string, len(headers))\n for i, h := range headers {\n if val, ok := rec[h]; ok {\n row[i] = fmt.Sprintf("%v", val)\n } else {\n row[i] = ""\n }\n }\n fmt.Fprintln(w, strings.Join(row, "\\t"))\n }\n\n w.Flush()\n}\n\n