2 puntos por GN⁺ 2023-12-10 | 1 comentarios | Compartir por WhatsApp
  • JC es una herramienta que convierte a JSON la salida de varias herramientas CLI, formatos de archivo y cadenas de texto comunes para facilitar su parsing en scripts
  • Recibe entrada por pipe y emite JSON por STDOUT; también admite magic syntax, que se antepone al comando, como en jc dig example.com
  • La salida predeterminada usa un esquema estricto por parser y convierte números, null, booleanos y campos semánticos adicionales; se puede acceder al JSON crudo previo al preprocesamiento con -r o raw=True
  • También puede usarse como biblioteca de Python, por lo que los resultados pueden recibirse como diccionarios de Python, listas de diccionarios o un iterable diferido en vez de JSON
  • Para pipelines grandes o de larga ejecución, ofrece parsers de streaming que emiten JSON Lines/NDJSON, --slurp, --meta-out, plugins de parsers locales y una opción para suprimir advertencias de compatibilidad de plataforma

Qué hace JC

  • JC(JSON Convert) convierte a JSON la salida de varias herramientas CLI, formatos de archivo y cadenas de texto comunes
  • La salida convertida puede conectarse por pipe con herramientas como jq o jello para procesamiento adicional
  • El ejemplo usa dig example.com | jc --dig para convertir la salida de dig en un arreglo JSON y luego jq -r '.[].answer[].data' para extraer solo las direcciones IP
  • La misma tarea también puede ejecutarse con magic syntax, como jc dig example.com | jq -r '.[].answer[].data'

Formas de uso como CLI y biblioteca de Python

  • El uso básico de la CLI consiste en recibir entrada por pipe e imprimir una representación JSON
    • COMMAND | jc [SLICE] [OPTIONS] PARSER
    • cat FILE | jc [SLICE] [OPTIONS] PARSER
    • echo STRING | jc [SLICE] [OPTIONS] PARSER
  • La magic syntax antepone jc a un comando o archivo de /proc con el formato jc [SLICE] [OPTIONS] COMMAND o jc [SLICE] [OPTIONS] /proc/<path-to-procfile>
    • No admite alias de comandos ni builtins del shell
  • En Python se llama como jc.parse('dig', cmd_output)
    • El valor devuelto puede ser un diccionario de Python, una lista de diccionarios o un iterable diferido de un parser de streaming, no una cadena JSON
  • La documentación del paquete de Python puede consultarse con help('jc'), help('jc.lib') o en la documentación en línea

Representación de salida y esquema

  • La representación predeterminada usa un esquema estricto por parser
    • Los números conocidos se convierten en valores JSON int o float
    • Los valores None conocidos se convierten en JSON null
    • Los valores booleanos conocidos también se convierten
    • Algunos parsers agregan campos semánticos adicionales
  • Se puede acceder al JSON crudo previo al preprocesamiento con la opción -r de la CLI o parse(..., raw=True) en la biblioteca de Python
  • El esquema de cada parser puede consultarse en los enlaces de documentación junto a la lista de parsers
  • La salida JSON está en formato compact de forma predeterminada; se puede usar el formato pretty con la opción -p
  • También es posible generar salida YAML con las opciones -y o --yaml-out

Parsers compatibles y ejemplos de uso

  • El soporte incluye comandos CLI, formatos de archivo y parsers de cadenas de texto
  • La lista de parsers del README incluye dig, ls, ping, ps, netstat, ifconfig, csv, xml, yaml, /etc/hosts, /etc/passwd, /proc/, systemctl, git log, jwt, url, semver, archivos relacionados con x509, entre otros
  • Los ejemplos de salida muestran cómo convertir varias entradas en estructuras JSON
    • La salida de arp se convierte en campos de dirección, tipo de hardware, dirección MAC e interfaz
    • Un archivo CSV se convierte en un arreglo de objetos con los encabezados como claves
    • /etc/hosts se convierte en IPs y arreglos de hostnames
    • ifconfig se convierte en objetos que incluyen nombre de interfaz, MTU, direcciones IPv4/IPv6, contadores de paquetes y bytes, etc.
    • ping se convierte en objetos que incluyen cantidad de paquetes transmitidos y recibidos, tasa de pérdida, tiempo de ida y vuelta y lista de respuestas
  • En Ansible puede usarse como filter plugin de la colección community.general

Instalación

  • jc puede instalarse de varias formas
    • pip3 install jc
    • Repositorios de paquetes del sistema operativo
    • Binarios por arquitectura en GitHub Releases
  • Los ejemplos de instalación por paquete del sistema operativo incluyen
    • Debian/Ubuntu: apt-get install jc
    • Fedora: dnf install jc
    • openSUSE: zypper install jc
    • Arch: pacman -S jc
    • macOS: brew install jc
    • FreeBSD: instalación mediante ports
    • Ansible filter plugin: ansible-galaxy collection install community.general
    • FortiSOAR connector: instalación desde FortiSOAR Connector Marketplace

Opciones principales

  • -a / --about: imprime información de jc y de los parsers en JSON o YAML
  • -d / --debug: imprime mensajes de seguimiento cuando hay problemas de parsing; -dd ofrece depuración más detallada
  • -h / --help: imprime la ayuda; con jc -h --parser_name se consulta la documentación del parser
  • -M / --meta-out: agrega metadatos a la salida, como timestamp, nombre del parser, magic command y código de salida del magic command
  • -q / --quiet: suprime mensajes de advertencia del parser; -qq ignora errores de parsers de streaming
  • -s / --slurp: agrupa entradas de varias líneas en un arreglo
  • -u / --unbuffer: desactiva el buffering de salida
  • -B / --bash-comp, -Z / --zsh-comp: genera un completion script para Bash o Zsh

Slice y Slurp

  • Slice procesa solo una parte de las líneas de entrada con una sintaxis START:STOP similar al slicing de Python
  • Por ejemplo, en una tabla con encabezado y pie, se puede convertir solo la data intermedia a JSON con jc 1:-1 --asciitable o jc 1:4 --asciitable
  • Los slices positivos y vacíos son los más eficientes en memoria, mientras que los slices negativos usan más memoria
  • Slurp permite que los parsers de cadenas que reciben entrada de una sola línea emitan varios elementos a la vez como un arreglo
    • Ej.: procesar con jc --slurp --ip-address un archivo con varias direcciones IP, una por línea
  • La magic syntax de /proc admite slurp automáticamente al seleccionar varios archivos
    • Al convertir varios archivos de /proc, se agrega el campo _file para identificar a qué archivo corresponde cada objeto de resultado
  • Al usar --meta-out junto con slurp, se imprime una estructura envuelta con un arreglo result y un objeto _jc_meta

Códigos de salida y metadatos

  • Los errores fatales internos de jc generan el código de salida 100; en caso contrario, devuelve 0
  • Al usar magic syntax, jc guarda el código de salida del programa que se está parseando y lo suma al código de salida de jc
    • Si ifconfig devuelve 1 y jc devuelve 0, el código de salida combinado es 1
    • Si ifconfig devuelve 0 y jc devuelve 100, el código de salida combinado es 100
    • Si ambos fallan, el código de salida combinado es 101
  • Al usar --meta-out o -M con magic syntax, el objeto _jc_meta incluye información del magic command y su código de salida

Colores y variables de entorno

  • La variable de entorno JC_COLORS permite especificar colores para key name, keyword, number y string
    • El formato es JC_COLORS=<keyname_color>,<keyword_color>,<number_color>,<string_color>
    • Los valores de color deben ser uno de black, red, green, yellow, blue, magenta, cyan, gray, brightblack, brightred, brightgreen, brightyellow, brightblue, brightmagenta, brightcyan, white, default
  • Si se establece la variable de entorno NO_COLOR, la salida con color se desactiva
  • La opción -C tiene prioridad sobre la variable de entorno NO_COLOR y la opción -m, y fuerza la salida con color

Parsers de streaming

  • La mayoría de los parsers leen todo STDIN en memoria, lo parsean y luego serializan el documento JSON de salida
  • Algunos parsers de streaming procesan la entrada línea por línea en cuanto la reciben y emiten JSON Lines o NDJSON
    • Ej.: ls-s, ping-s
    • Pueden reducir mucho el uso de memoria para salidas grandes de comandos como ls -lR / y, en algunos casos, procesar más rápido
  • Los parsers de streaming no pueden usarse con magic syntax
  • Para evitar que los errores de parsing rompan el pipe en pipelines de larga ejecución, se puede usar -qq o ignore_exceptions=True en Python
    • Las líneas exitosas incluyen _jc_meta.success: true
    • Las líneas fallidas incluyen _jc_meta.success: false, error y line
  • Si la salida aparece con demora entre pipes debido al buffer del sistema operativo, se puede usar salida sin buffer con -u
    • Sin embargo, en streams de datos grandes, la salida sin buffer puede ser más lenta
  • En Python, los parsers de streaming reciben una entrada iterable y devuelven un objeto iterable para procesamiento diferido

Plugins de parsers

  • Un parser plugin local puede colocarse en la carpeta jc/jcparsers dentro del directorio de datos de la app
    • Linux/unix: $HOME/.local/share/jc/jcparsers
    • macOS: $HOME/Library/Application Support/jc/jcparsers
    • Windows: $LOCALAPPDATA\jc\jc\jcparsers
  • Los plugins son archivos de módulo estándar de Python
  • Se puede usar jc/parsers/foo.py o jc/parsers/foo_s.py como plantilla
  • El nombre de archivo del plugin debe ser un nombre válido de módulo de Python, comenzar con una letra y contener solo caracteres alfanuméricos y underscore
  • Los plugins locales pueden overridear parsers predeterminados

Locale, zona horaria y compatibilidad

  • Para obtener los mejores resultados, se recomienda configurar LC_ALL en C o en_US.UTF-8
  • En algunos sistemas antiguos, el locale C no soporta codificación UTF-8, por lo que la salida UTF-8 puede degradarse a ASCII y secuencias de escape \\u
  • Algunos parsers agregan campos calculados de epoch timestamp
    • Si el nombre del campo no tiene el sufijo _utc, se considera un timestamp naive basado en la zona horaria local
    • Si se detecta zona horaria UTC en el texto de salida del comando, se convierte en un timestamp aware y el nombre de la clave lleva el sufijo _utc
    • Las zonas horarias distintas de UTC no se soportan como timestamps aware
  • Algunos parsers convierten salidas específicas de cada plataforma, por lo que imprimen advertencias si se ejecutan en plataformas no soportadas
  • Incluso en plataformas no soportadas, se pueden parsear archivos de salida de otros sistemas; en ese caso, las advertencias pueden suprimirse con -q o quiet=True en Python
  • Las plataformas probadas incluyen Centos 7.7, Ubuntu 18.04/20.04, Fedora32, macOS 10.11.6/10.14.6, NixOS, FreeBSD12, Windows 10, Windows 2016 Server y Windows 2019 Server

1 comentarios

 
GN⁺ 2023-12-10
Opiniones de Hacker News
  • En FreeBSD, este problema está resuelto en cierta medida con libxo:
    se puede obtener salida estructurada con algo como $ ps --libxo=json | jq
    No es perfecto: había soporte para ls, pero se eliminó por alguna razón, y no todas las utilidades lo soportan.
    jc parece una excelente solución temporal que ofrece parsers para muchos comandos, pero tiene la limitación de parsear salidas de texto que, de entrada, no fueron diseñadas para ser parseadas.
    Sería bueno que las utilidades convergieran en emitir salida estructurada mediante una bandera común; aunque hacer que eso sea el valor predeterminado, como en PowerShell, sería demasiado para Unix/Linux, una bandera estándar --json ya sería un gran avance.
    https://wiki.freebsd.org/LibXo
    https://reviews.freebsd.org/D13959

    • PowerShell va un paso más allá de JSON y soporta objetos modificables reales.
      Entiendo que no se limita a pasar datos estructurados, sino que hace fluir objetos opacos por el pipeline y hasta permite volver a una etapa anterior para llamar métodos: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_methods?view=powershell-7.4
      Me gustan wrappers como jc, libxo y shells experimentales como https://www.nushell.sh/, pero se enfocan más en el paso de datos que en objetos con métodos ejecutables.
      Los datos estructurados todavía se sienten muy Unix; si de verdad se necesitan objetos, creo que es momento de abrir Python o Ruby.
      Por muy bueno que sea un shell y por buenas que sean sus funciones de programación, es importante saber cuándo pasar de un script de shell a un lenguaje de programación hecho y derecho.
    • Una entrada de blog del autor original trata justamente este tema.
      jc se presenta como una herramienta que cubre ese rol por ahora, y se entiende como un puente hasta que el soporte de -j/--json se extienda ampliamente por las herramientas Unix.
      https://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/
    • En SerenityOS también ocurre algo similar: las entradas bajo /proc devuelven datos JSON en lugar de archivos de texto no estructurado.
      Una solución más estructural podría ser permitir que ELF exponga estructuras de datos, serializar esas estructuras como salida de terminal y luego dejar que el usuario las emita o procese en el formato que quiera, como JSON o YAML.
    • Libxo suena genial en teoría, pero parece que, en vez de que la aplicación le pase la estructura a libxo y le delegue el formateo, cada aplicación tiene que implementar por su cuenta la lógica de cada formato de salida.
      No recuerdo la utilidad exacta, quizá era iostat, pero formateaba las líneas de salida JSON mediante interpolación de strings y, con cierta combinación de banderas, generaba una salida completamente rota.
      No sé si ahora mejoró, pero cuando había una opción de intervalo esperaba algo tipo JSON lines.
      En términos de usabilidad, creo que PowerShell y kubectl están muy por delante de libxo.
    • Libxo viene incluido en el sistema base de FreeBSD, pero también se puede usar de forma general.
      https://github.com/Juniper/libxo
      https://libxo.readthedocs.io/en/latest/
  • La idea es muy buena, pero pensar en el mantenimiento me inquieta.
    Considerando cambios de salida según versiones y banderas de comandos, parece un infierno de mantenimiento; en la práctica funcionará bien en algunos casos, pero más allá de los escenarios básicos probablemente pierda rápido el encanto.
    Además, usar -- para las opciones de la herramienta no se ve bien.
    Si cada parser nuevo necesita una bandera nueva, la ayuda o las páginas de manual podrían terminar con miles de líneas.

    • Si se lo ve como un shim cuyo alcance se irá reduciendo a medida que las utilidades de línea de comandos empiecen a soportar cada vez más salida JSON, suena razonable.
      Cuando alguna utilidad decida soportar su propia exportación a JSON, después esta herramienta puede delegarle esa función.
    • Si lees más abajo en la documentación, también se puede anteponer jc al comando, como en jc ls.
      El parámetro --cmd permite procesar los datos antes de convertirlos, así que en realidad es una buena idea.
      Por ejemplo, podrías querer aplicar grep a una lista antes de la conversión.
      Desde el punto de vista del mantenimiento, si la salida de los comandos Unix básicos cambiara mucho, rompería no solo esta herramienta sino también muchísimos scripts, así que no parece que vaya a romperse tan seguido por actualizaciones de otros binarios.
    • Por otro lado, me genera sentimientos encontrados.
      Un único paquete común de parsers bien mantenido es mejor que parsers improvisados de salida desperdigados por todos lados, pero cuando necesito salida JSON directa para hacer algo complejo, preferiría que el parseo en sí no agregue más problemas.
      Para usar esto con confianza, al final supongo que bastaría con enviar PR con casos de prueba adicionales para todo lo que quiera usar.
      De todos modos, esas pruebas las habría tenido que escribir yo.
    • Esto requiere colaboración.
      No queda otra que la gente envíe la información de parseo de las herramientas que necesita y que quienes las usan puedan mantenerla actualizada fácilmente.
    • Es uno de los mejores usos de los LLM, que han demostrado buena capacidad para convertir texto no estructurado en objetos estructurados.
  • Nushell adopta un enfoque distinto, pero llega más o menos al mismo punto: datos estructurados de comandos de shell.
    Principalmente, el propio shell se encarga de ese papel.
    http://www.nushell.sh/

    • Nushell tiene una operación from json, así que en la práctica encaja muy bien con JC.
      Hace tiempo grabé un video mostrando algunas buenas funciones de Nushell, y alrededor del minuto 19 hablo de combinarlo con jc: https://www.youtube.com/watch?v=KF5dtxVsn1E
    • Desde que se anunció Nushell por primera vez, lo había revisado de vez en cuando, pero recién hace uno o dos meses por fin entendí bien la idea central.
      Estaba por escribir un script para revisar archivos dentro de directorios que coincidieran con cierto patrón y eliminar aquellos cuyas horas de modificación estuvieran a menos de 10 minutos entre sí, y recordé que Nushell era bueno para este tipo de cosas.
      Lo probé un rato y por fin me hizo clic; ahora estoy enganchado.
      Incluso con datos no estructurados, si puedes convertirlos a algo como una lista de registros y procesarlos, es muy potente.
  • En cierto sentido, es genial que haya archivos en todas partes; esa es la promesa de Unix, y en Plan 9 aparece en una forma aún más extendida.
    Pero el hecho de que sean archivos no estructurados, o que cada archivo tenga su propio formato, también termina siendo un freno igual de grande.
    Incluso intentar parsear un solo archivo de logs de nginx solo con algo como awk puede ser molesto.
    Una de las grandes desventajas es que en el espacio de usuario de Linux es difícil llevar a cabo reescrituras de sistemas a gran escala o cambios de diseño.
    Quiero un shell más inteligente, me gustan los archivos y también tengo el libro de awk a la mano, pero creo que ya es hora de una mejora seria en parseo de datos.
    Así como un programa puede decidir si renderiza o no salida con colores, estaría bien que también pudiera decidir si emitir o no salida estructurada.

    • Parte del problema es que la salida de los comandos es a la vez interfaz de usuario y API.
      Como cualquier UI de texto puede usarse como API, el texto fácil de leer para humanos obtiene prioridad.
      Por eso hacer scripts de shell se parece a crear extensiones de navegador de terceros.
      Miras, haces una suposición, improvisas un parser con base en esa suposición y esperas que funcione.
      Sería bueno tener una tercera salida estándar para contenido legible por máquinas.
      La terminal no la mostraría por defecto; al usar pipes, esa salida sería la que se pipea; debería ser JSONL, y la página del manual especificaría el contrato.
      Así, la salida estándar podría quedarse para humanos, y el parseo sería algo posible, pero uno sabría que es frágil.
      Claro, es una idea que rompería totalmente la compatibilidad hacia atrás, y si reinventáramos de forma poco realista la CLI desde cero para modernizarla, habría una larga lista de cosas que querría cambiar.
    • Hace medio siglo surgieron ideas bastante buenas, y es frustrante que mucha gente no las vea como algo a mejorar, sino que las acepte como evangelio.
      También es realmente difícil conseguir mejoras, y cuando alguien como Lennart intenta limpiar restos obsoletos de hace décadas, enseguida se arma el drama.
      JSON tampoco es perfecto todavía.
      JSON es una idea razonablemente buena, pero para hacerlo bien se necesita un formato que pueda exponer cosas como tipos de datos.
      Tendría que ser algo más cercano a PowerShell, para tratar los números como números y poder hacer cosas sorprendentes como calcular la diferencia entre fechas con $a - $b.
    • Una mejora más sencilla sería implementar salida JSON mediante un argumento de línea de comandos en todas las herramientas CLI.
      Sería bueno que esto ocurriera en GNU coreutils.
    • Siempre está la opción de PowerShell.
      El problema es que está tan profundamente arraigado en .NET y en objetos que resulta muy difícil integrarlo con comandos nativos existentes en cualquier plataforma.
    • En el parseo de logs de servidor, es una lástima que no se puedan separar estas funciones de algo como logstash.
      Porque, en esencia, ya están haciendo lo mismo.
      Aunque el objetivo final probablemente sea que las herramientas superiores vean el valor y ofrezcan directamente salida estructurada.
  • Excelente.
    Apoyo mucho las herramientas CLI que soportan salida JSON razonable, y cosas como https://github.com/WireGuard/wireguard-tools/blob/master/contrib/json/wg-json y |ConvertTo-Json de PowerShell son una parte importante de la automatización de administración/monitoreo.
    Pero aquí razonable hace mucho trabajo, y la realidad es la realidad.
    Como la forma de LSI/Broadcom StorCLI de agregar J después del comando, o los wrappers de PowerShell que ocultan COM: técnicamente son JSON, pero el resultado es tan absurdamente complejo o inútil que muchas veces uno termina volviendo al parche de “mejor pasarle unas regex a la salida en texto plano”.
    Aun así, definitivamente voy a revisar esto.
    Si el primer ejemplo, parsear la salida de dig, representa algo que se puede hacer de forma confiable, podría ser bastante interesante.

  • Creo que jc dig example.com debería ser la sintaxis predeterminada.
    Porque con dig example.com | jc --dig, para parsear la salida luego hay que adivinar las flags y parámetros del comando anterior.

  • Que toda salida sea un objeto es una de las cosas que más me gustan de PowerShell.
    Lo extraño cada vez que tengo que escribir un script en bash.

  • Mis respetos para quien decidió mantener esto.

    • Mis respetos también para quien decidió usar esta herramienta enfrentándose en la práctica a las suposiciones equivocadas que haga.
    • Me pregunto cómo manejarán los problemas de versiones.
      Algo como aws s3 ls | jc --aws=1.2.3 sería una pesadilla.
    • Buen punto.
      Me recuerda al programa file de Linux o Unix, y a los héroes que lo mantienen.
    • En teoría, si se pudieran cargar cosas como plugins, por ejemplo teniéndolos como comandos de shell separados, se podría trasladar parte del esfuerzo de mantenimiento a los autores de plugins.
  • Me pregunto si alguien conoce una lista de herramientas modernas de línea de comandos de Unix que acepten la opción --json.
    También podría ser útil agregar esa información a este repositorio.

    • En FreeBSD, prácticamente casi todo lo soporta a través de libxo.
    • No tengo una lista, pero ip, el reemplazo moderno de ifconfig, soporta JSON.
      lldpctl también lo soporta.
      Ansible ofrece los detalles del sistema como JSON llamado facts, y está diseñado para que la automatización los aproveche.
    • lsblk acepta la bandera --json y puede dar mucha información.
      Puedes probar lsblk --json --output-all.
      Es muy útil cuando un script necesita revisar los discos y particiones del sistema.
    • TShark, la herramienta CLI complementaria de Wireshark, lo soporta con la bandera -T json.
    • Quizá no sea la respuesta esperada, pero está AWS CLI.
  • Es un proyecto interesante.
    Pero pensé que usaría algo como textfsm como parser de primera etapa.
    textfsm se usa mucho para parsear la salida CLI de equipos de red.
    https://github.com/google/textfsm