
Hoy toca hablar de tres herramientas que, sin ser nuevas, forman una combinación tan potente que yo llamo el tridente JSON de Linux. Te hablo de jc, jq y gron. Y no, no son tres amigos míos, aunque a lo mejor jc podría serlo. La historia empieza con el episodio anterior. Te conté cómo montar una automatización matutina con el nightly-runner, y en ese proceso necesitaba extraer información del sistema. Hasta ahí todo normal. Lo que pasa es que me di cuenta de que la forma tradicional de hacerlo es con awk, sed, cut y otras herramientas que, funcionan, sí, pero son frágiles. Muy frágiles. Un cambio en el orden de las columnas de ps aux y tu script se va al garete.
Además, quería pasarle esa información a un modelo de lenguaje. Y claro, un modelo de lenguaje entiende mucho mejor un JSON bien estructurado que una salida de texto plano con columnas desiguales. Así que empecé a buscar herramientas que me ayudaran a convertir la salida de comandos Linux en JSON de forma limpia y fiable. Y ahí encontré jc.
El problema de la salida en texto plano
Tradicionalmente, los comandos de Linux muestran la información en texto plano con columnas. ps aux, df -h, free, ss, systemctl… todos te devuelven tablas de texto que están pensadas para ser leídas por un humano, no por una máquina.
Para extraer información de esas tablas, los administradores de sistemas hemos usado siempre awk, sed, cut y grep. Y funciona, no te voy a engañar. Pero tiene un problema grave: si cambia el orden de las columnas, si añaden una columna nueva o si el formato varía entre versiones, tu script se rompe.
Y luego está el tema de los modelos de lenguaje. Si quieres pasarle la información de tu sistema a un modelo como Llama 3.2, lo ideal es dársela en un formato estructurado. Un JSON con campos claros, sin ambigüedades. Porque si le pasas la salida de ps aux tal cual, el modelo puede interpretar mal las columnas o confundir unos datos con otros.
jc: de texto plano a JSON
jc es la herramienta que convierte la salida de comandos Linux a JSON. Se instala con pip. No necesita permisos de root, no requiere configuración, no necesita un daemon corriendo en segundo plano. Simplemente lo instalas y empiezas a usarlo.
pip install jc
La primera vez que lo usas te cambia la perspectiva. Llevas años haciendo ps aux | awk '{print $2, $11}' y de repente haces ps aux | jc --ps | jq '.[] | {pid, command}' y todo cobra sentido. Los campos tienen nombre, no dependen de su posición en la salida, y puedes encadenar operaciones de forma limpia y predecible.
jc funciona detectando automáticamente el comando que le pasas y aplicando el parser correspondiente. Pero también puedes forzar un parser concreto si la detección automática falla. Y como te he dicho, si no hay parser para tu comando, puedes crear uno. La comunidad ha ido añadiendo parsers con el tiempo, y la lista sigue creciendo.
Y luego simplemente haces un pipe:
ps aux | jc --ps
Y ya tienes un JSON con todos los procesos, cada campo perfectamente identificado: pid, cpu, mem, rss, command, user… todo.
El funcionamiento por debajo es sencillo. jc tiene parsers específicos para cada comando. Más de 60 parsers incluidos de serie. Desde los más comunes como ps, df, free, ss, systemctl, lsblk, hasta otros más específicos como crontab, last, lsof, pip list, lsmod, date, y muchos más.
jc -a
Este comando te lista todos los parsers disponibles. Y si ninguno te sirve, puedes crear el tuyo propio. jc está escrito en Python, con licencia MIT, y acepta parsers personalizados.
La gran ventaja de jc frente a awk es que el JSON no depende del orden de las columnas. Aunque la versión de ps que tengas muestre las columnas en otro orden, jc sigue devolviendo el mismo JSON estructurado. Tu script no se rompe.
Puedes usar jc tanto con entrada estándar como con archivos:
df -h > /tmp/df.txt
jc --df /tmp/df.txt
Esto te permite guardar la salida de comandos en un momento dado y procesarla más tarde, o pasarle archivos históricos.
jq: la navaja suiza de los JSON
Si jc es el que genera los JSON, jq es el que los procesa. jq es a JSON lo que sed es a texto: el procesador por defecto. Está escrito en Go y es maduro, estable y rapidísimo.
Con jq puedes filtrar, seleccionar, ordenar, agrupar, mapear y transformar cualquier JSON. La sintaxis es potente y expresiva, aunque a veces puede resultar liosa. Pero como te contaré más adelante, para eso están los modelos de lenguaje.
La sintaxis de jq merece que le dediques un tiempo, pero no te preocupes si al principio te resulta críptica. Es normal. A mí también me pasó. Lo bueno es que puedes pedirle a cualquier modelo de lenguaje que te genere la expresión de jq que necesitas. Le explicas qué JSON tienes y qué quieres extraer, y él te devuelve el comando.
Algunos ejemplos de lo que puedes hacer combinando jc y jq:
# Procesos que más RAM consumen
ps aux | jc --ps | jq '.[] | select(.rss > 100000) | {user, command, rss}'
# Discos con más del 80% de uso
df -h | jc --df | jq '.[] | select(.use_percent | tonumber > 80)'
# Servicios que han fallado
systemctl --all | jc --systemctl | jq '.[] | select(.state == "failed")'
Todo en una sola línea. Sin awk, sin sed, sin scripts frágiles. Y con la tranquilidad de que el formato es siempre el mismo.
Los filtros de jq se pueden encadenar con pipes, igual que en la shell. Puedes seleccionar campos, filtrar por condiciones, ordenar resultados, agrupar por campos y limitar la salida. Todo en una expresión que, aunque parezca compleja al principio, sigue una lógica muy clara una vez que le coges el truco.
También puedes construir JSONs nuevos desde cero:
jc --ps | jq '{total_procesos: length, procesos_por_usuario: [group_by(.user)[] | {user: .[0].user, count: length}]}'
Este comando transforma 500 líneas de procesos en un JSON resumen con el total y el recuento por usuario. En una sola línea.
Los parsers más útiles de jc
jc incluye parsers para más de 60 comandos, pero hay algunos que vas a usar más que otros. Aquí te dejo los que a mí me han resultado más útiles:
--ps: procesos del sistema. Te da pid, cpu, mem, rss, command, user, time…--df: discos. Filesystem, size, used, avail, use_percent, mount…--free: memoria. Total, used, free, shared, buff/cache, available…--ss: sockets. Netid, state, recv_q, send_q, local, peer, process…--systemctl: servicios. Unit, load, active, sub, description, state…--lsblk: dispositivos de bloque. Name, maj:min, rm, size, ro, type, mountpoint…--ifconfig: interfaces de red. Name, flags, mtu, mac, inet, inet6…--uptime: tiempo de actividad. Time, uptime, users, load_1m, load_5m, load_15m…--last: últimos inicios de sesión. User, tty, from, login_at, logout_at…--crontab: tareas programadas. Minute, hour, day_of_month, month, day_of_week, command…
Cada parser tiene su propia estructura de salida, pero todos siguen el mismo patrón: un array de objetos con campos claramente nombrados. Esto hace que encadenar varios comandos y procesarlos con jq sea trivial.
gron: haz greppable cualquier JSON
Y luego está gron, que para mí era un absoluto desconocido hasta que empecé a preparar este episodio. gron hace una cosa muy simple pero increíblemente útil: aplana un JSON.
¿Por qué querrías aplanar un JSON? Porque los JSONs suelen ser profundos y anidados, con objetos dentro de objetos. Si haces un curl a una API y quieres buscar un valor concreto, no puedes hacer grep porque todo el JSON está en una sola línea o en un árbol complejo.
gron convierte esto:
{"weather": {"temp": 28, "humidity": 65}, "wind": {"speed": 12}}
En esto:
json.weather.temp = 28
json.weather.humidity = 65
json.wind.speed = 12
Ahora cada valor tiene su propia línea, y puedes hacer grep de forma natural:
curl "https://wttr.in/Madrid?format=j1" | gron | grep temp
Y no solo eso. gron también hace la operación inversa. Puedes editar el JSON aplanado con sed, y luego volver a convertirlo a JSON con gron --ungron. Esto te permite modificar JSONs de forma sencilla usando herramientas de texto tradicionales.
La combinación de jc, jq y gron te cubre todo el ciclo de vida del JSON: generación (jc), procesamiento (jq), y exploración (gron).
sysreport.py: todo en uno
Con estas tres herramientas como base, he creado un script en Python que llamo sysreport.py. Lo que hace es ejecutar varios comandos del sistema, convertir sus salidas a JSON con jc, y agruparlo todo en un único archivo JSON estructurado.
python3 sysreport.py
Esto genera /tmp/sysreport.json con toda la información del sistema: procesos, discos, memoria, servicios, puertos, usuarios, tiempo de actividad… TODO.
Luego puedes consultar ese JSON con jq:
cat /tmp/sysreport.json | jq '.memoria'
O pasárselo directamente a un modelo de lenguaje para que lo analice.
#!/usr/bin/env python3
"""
sysreport.py — Genera un reporte completo del sistema en JSON.
Usa jc para convertir la salida de comandos del sistema a JSON
estructurado. Ideal para diagnosticar, auditar o pasar a una IA.
Uso:
python3 sysreport.py # Guarda en /tmp/sysreport.json
python3 sysreport.py --stdout # Imprime por stdout
python3 sysreport.py --compact # Sin pretty-print
python3 sysreport.py --only discos # Solo una sección
Dependencias:
- Python 3.10+
- jc (pip install jc)
- jq (sudo apt install jq)
- Los comandos del sistema: ps, df, systemctl, ss, free, uptime
"""
import argparse
import json
import subprocess
import sys
from datetime import datetime
# ─── Secciones del reporte ────────────────────────────────────────────────────
SECCIONES = {
"procesos": (["ps", "aux"], "ps"),
"discos": (["df", "-h"], "df"),
"servicios": (["systemctl", "list-units", "--no-legend"], "systemctl"),
"puertos": (["ss", "-tlnp"], "ss"),
"memoria": (["free", "-h"], "free"),
"uptime": (["uptime"], "uptime"),
}
def ejecutar_jc(comando: list, parser: str) -> list | dict:
"""Ejecuta un comando y lo parsea con jc."""
try:
out = subprocess.run(comando, capture_output=True, text=True, timeout=10)
if out.returncode != 0:
return {"error": f"Comando falló: {' '.join(comando)}"}
# Parsear con jc
jc_out = subprocess.run(
["jc", f"--{parser}"],
input=out.stdout,
capture_output=True,
text=True,
timeout=10,
)
if jc_out.returncode != 0:
return {"error": f"jc falló para {parser}: {jc_out.stderr[:200]}"}
return json.loads(jc_out.stdout)
except subprocess.TimeoutExpired:
return {"error": f"Timeout ejecutando {' '.join(comando)}"}
except json.JSONDecodeError as e:
return {"error": f"JSON inválido de jc --{parser}: {e}"}
except FileNotFoundError:
return {"error": f"jc no encontrado. Instala: pip install jc"}
def generar_reporte(secciones: list[str] | None = None) -> dict:
"""Genera el reporte del sistema."""
incluir = secciones if secciones else list(SECCIONES.keys())
reporte = {
"fecha": datetime.now().isoformat(),
"hostname": subprocess.run(
["hostname"], capture_output=True, text=True, timeout=5
).stdout.strip(),
}
for seccion in incluir:
if seccion in SECCIONES:
comando, parser = SECCIONES[seccion]
reporte[seccion] = ejecutar_jc(comando, parser)
return reporte
# ─── Main ─────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(
description="sysreport: reporte del sistema en JSON usando jc",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""\
Ejemplos:
python3 sysreport.py # Guarda en /tmp/sysreport.json
python3 sysreport.py --stdout # Imprime por stdout
python3 sysreport.py --only procesos discos # Solo procesos y discos
python3 sysreport.py --stdout | jq '.discos' # Filtrar con jq
""",
)
parser.add_argument("--stdout", action="store_true", help="Imprimir por stdout en vez de guardar")
parser.add_argument("--compact", action="store_true", help="Sin pretty-print (más pequeño)")
parser.add_argument("--only", nargs="+", choices=list(SECCIONES.keys()),
help=f"Solo estas secciones: {', '.join(SECCIONES.keys())}")
args = parser.parse_args()
print("📡 Generando reporte del sistema...", file=sys.stderr)
reporte = generar_reporte(args.only)
indent = None if args.compact else 2
if args.stdout:
print(json.dumps(reporte, indent=indent, ensure_ascii=False))
else:
output_path = "/tmp/sysreport.json"
with open(output_path, "w") as f:
json.dump(reporte, f, indent=indent, ensure_ascii=False)
print(f"✅ Reporte guardado en {output_path}", file=sys.stderr)
print(f"📦 Tamaño: {len(json.dumps(reporte))} caracteres", file=sys.stderr)
print(f" Secciones: {', '.join(reporte.keys())}", file=sys.stderr)
# Mostrar resumen rápido
errores = [k for k, v in reporte.items() if isinstance(v, dict) and "error" in v]
if errores:
print(f"⚠️ Errores en: {', '.join(errores)}", file=sys.stderr)
# Contar elementos por sección
for k, v in reporte.items():
if isinstance(v, list):
print(f" {k}: {len(v)} elementos", file=sys.stderr)
if __name__ == "__main__":
main()
Preguntando al sistema en lenguaje natural
Y aquí llega lo bueno. El script sysreport.py incluye un modo interactivo donde puedes hacer preguntas en lenguaje natural sobre el estado de tu sistema.
python3 sysreport.py --ask "¿Qué procesos consumen más RAM?"
Lo que hace por debajo es bien sencillo. Carga el JSON del sistema, lo mete en un prompt junto con la pregunta, y se lo envía a Ollama con Llama 3.2. El modelo analiza los datos y responde en lenguaje natural.
El proceso que más RAM consume es OBS Studio con un 32.7%. Le sigue Firefox con un 15.2%. El resto de procesos están dentro de lo normal.
O puedes preguntarle:
python3 sysreport.py --ask "¿Hay algún disco lleno o servicios caídos?"
Y te responde con un diagnóstico completo. Todo en local, sin enviar datos a ningún servidor externo.
Puedes hacer tantas preguntas como quieras. Una vez que el modelo está cargado en memoria, las respuestas son prácticamente instantáneas. En el episodio hago una demo en vivo donde primero le pregunto por los procesos que más RAM consumen, luego por los servicios caídos y luego le pido un resumen general del sistema. Todo en cuestión de segundos.
El prompt del sistema es clave. Le digo que es un administrador de sistemas y que debe analizar el JSON y responder en lenguaje natural. Con eso basta. No necesita instrucciones más complejas porque el modelo entiende el contexto.
El corazón del script es una llamada a la API de Ollama:
import requests, json
datos = json.load(open("/tmp/sysreport.json"))
pregunta = "¿Qué procesos consumen más RAM?"
respuesta = requests.post("http://localhost:11434/api/chat", json={
"model": "llama3.2:3b",
"messages": [
{"role": "system", "content": "Eres un administrador de sistemas. Analiza el JSON y responde en lenguaje natural."},
{"role": "user", "content": f"Datos del sistema: {json.dumps(datos)}\n\n{pregunta}"}
]
})
print(respuesta.json()["message"]["content"])
Así de sencillo. Sin dependencias externas, sin APIs de pago, sin complicaciones.
Monitorización con watch
Cómo aprender jq sin volverte loco
Si nunca has usado jq, la sintaxis te va a parecer críptica. Es normal. A mí también me pasó. La clave es empezar con operaciones simples e ir subiendo la complejidad poco a poco.
Empieza por jq '.' que solo formatea el JSON. Luego prueba jq '.[]' para iterar sobre arrays. Luego jq '.[] | {campo1, campo2}' para seleccionar campos. Y luego añade filtros con select(.campo > valor). En cuestión de horas, estarás haciendo consultas complejas como si nada.
Y si te atascas, recuerda que puedes pedirle a cualquier modelo de lenguaje que te genere la expresión de jq. Le pasas un ejemplo del JSON y le dices lo que quieres extraer, y él te devuelve el comando.
sysreport.py en acción
En el episodio hago una demo en directo. Lanzo el script, que recoge información de ps, df, free, ss y systemctl, y genera un JSON con todo. Luego le pregunto qué proceso consume más RAM. La respuesta es OBS Studio con un 32.7%, que tiene toda la lógica porque estoy grabando el episodio.
Luego le pregunto por servicios caídos y aunque el modelo no encuentra ninguno, el sistema responde con un diagnóstico completo. Y finalmente le pido un resumen general del estado del sistema. En todos los casos, la respuesta es rápida, precisa y en lenguaje natural.
La segunda pregunta es mucho más rápida que la primera porque el modelo ya está cargado en memoria. Este detalle es importante si vas a usar el script de forma interactiva.
sysreport.py como API
Otra opción interesante es usar sysreport.py como una API local. Puedes lanzar el servidor y hacer consultas HTTP:
python3 sysreport.py --serve
Esto levanta un servidor en localhost:8000 donde puedes hacer consultas en lenguaje natural mediante POST. Perfecto para usarlo desde otros scripts, desde el móvil o desde cualquier aplicación que necesite información del sistema en lenguaje natural.
Monitorización con watch
watch -n 5 "jc --ps | jq '[.[] | select(.rss > 100000) | {command, rss}]'"
Esto actualiza cada 5 segundos la lista de procesos que consumen más de 100 MB de RAM. O puedes usar el sysreport con notify-send para recibir alertas en el escritorio:
python3 sysreport.py --check-disk --notify
Si algún disco supera el 80% de ocupación, te salta una notificación en el escritorio.
Ventajas de este enfoque
Todo esto tiene varias ventajas frente a las soluciones tradicionales. La primera es que es completamente local. No envías información de tu sistema a ningún servidor externo. La segunda es que no cuesta dinero. Todo corre en tu máquina, con tu modelo de lenguaje, sin APIs de pago. La tercera es que es modular. jc, jq y gron son herramientas independientes que puedes usar por separado o combinadas.
Y luego está el factor de la fiabilidad. Con awk, un cambio en la salida de ps aux rompe tu script. Con jc, el JSON es siempre el mismo. Da igual si actualizas el sistema, si cambias de distribución o si el comando modifica su formato de salida. jc se encarga de normalizarlo todo a JSON. Da igual la versión del comando, da igual la distribución, da igual el orden de las columnas. El JSON estructurado es predecible.
sysreport: integración con el nightly-runner
Si te viste el episodio 815, recordarás que el nightly-runner necesitaba un módulo para extraer información del sistema. Pues este es exactamente ese módulo. sysreport.py genera el JSON que el nightly-runner usa para incluir el estado del sistema en el resumen matutino.
La integración es directa. En el nightly-runner añades una llamada a sysreport:
import subprocess, json
subprocess.run(["python3", "sysreport.py"])
with open("/tmp/sysreport.json") as f:
datos_sistema = json.load(f)
Y esos datos se pasan al prompt de Llama 3.2 junto con el tiempo, las noticias y las zapatillas. El resultado es un resumen matutino que también te dice si el disco se está llenando o si hay algún proceso que se está comiendo toda la RAM.
Más allá del sistema
Aunque en este episodio me he centrado en la información del sistema, jc va mucho más allá. Puedes convertir a JSON desde consultas DNS hasta listados de paquetes de apt o pacman, pasando por archivos de configuración, crontabs, logs de sistema y mucho más.
Y jq, por supuesto, te sirve para cualquier JSON, venga de donde venga. APIs web, archivos de configuración, exportaciones de datos… es una herramienta universal.
gron es más específico, pero cuando necesitas explorar un JSON profundo o hacer búsquedas rápidas, no hay nada más rápido que un grep sobre un JSON aplanado.
Casos de uso reales
Para que veas el potencial de esta combinación, te dejo algunos casos de uso reales que he ido descubriendo mientras trasteaba con estas herramientas.
El primero es la monitorización de recursos. Con un alias en tu shell, puedes tener en una sola línea el estado de tu sistema:
alias sysinfo='echo "=== PROCESOS ===" && ps aux | jc --ps | jq "length" && echo "=== DISCOS ===" && df -h | jc --df | jq ".[] | {mount, use_percent}" && echo "=== MEMORIA ===" && free | jc --free'
El segundo es la detección de anomalías. Puedes comparar el estado actual con el de ayer y detectar cambios:
jc --ps > /tmp/procesos_hoy.json
diff <(cat /tmp/procesos_ayer.json | jq -c '.[] | {pid, command, rss}') <(cat /tmp/procesos_hoy.json | jq -c '.[] | {pid, command, rss}')
El tercero es la generación de informes. Con un script sencillo puedes generar un HTML con el estado del sistema:
echo "<html><body><h1>Informe del sistema</h1><pre>$(jc --ps | jq .)</pre></body></html>" > informe.html
Son ideas sencillas, pero te dan una idea de hasta dónde puedes llegar con estas tres herramientas combinadas.
Cierre
El tridente JSON no es una solución mágica, pero sí es una forma mucho más elegante y fiable de trabajar con información del sistema en Linux. Si hasta ahora estabas usando awk para todo, te animo a que pruebes jc. La primera vez que hagas ps aux | jc --ps | jq . y veas el JSON estructurado, entenderás de qué te hablo.
Si te animas a probar estas herramientas, empieza por lo básico. Instala jc y haz ps aux | jc --ps | jq .. Luego prueba con df, con free, con ss. Cuando te sientas cómodo, añade gron al repertorio. Y cuando domines los tres, el sysreport.py te va a parecer la consecuencia natural de todo lo aprendido.
Los scripts sysreport.py y los ejemplos de este episodio los dejaré en las notas para que puedas trastear con ellos. Y si se te ocurren más usos para esta combinación, ya sabes dónde encontrarme. En el grupo de Telegram de atareao con Linux siempre estamos dándole vueltas a este tipo de herramientas.
La combinación de jc, jq y gron te da un control sobre la información de tu sistema que con las herramientas tradicionales simplemente no tenías. Y cuando le añades un modelo de lenguaje para que interprete los datos, el resultado es una forma completamente nueva de interactuar con Linux.
Espero que te haya gustado el episodio, que lo hayas disfrutado y que le saques partido a estas herramientas. La vida son dos días y uno ya ha pasado, así que mejor tener herramientas que te hagan el trabajo más fácil.
Más información
- jc en GitHub — conversor de comandos Linux a JSON
- jq – Documentación oficial — el procesador de JSON por excelencia
- gron en GitHub — haz greppable cualquier JSON
- Ollama — ejecuta modelos de lenguaje en local
- Episodio 815 — automatización matutina con IA
- Episodio 813 — scraping con IA