2 points par GN⁺ 2023-12-10 | 1 commentaires | Partager sur WhatsApp
  • JC est un outil qui convertit en JSON la sortie de nombreux outils CLI, formats de fichiers et chaînes génériques afin de faciliter leur parsing dans des scripts
  • Il accepte une entrée via pipe et produit du JSON sur STDOUT, tout en prenant aussi en charge une magic syntax en le plaçant devant une commande, comme dans jc dig example.com
  • La sortie par défaut utilise un schéma strict propre à chaque parseur et convertit les nombres, null, les booléens ainsi que des champs sémantiques supplémentaires ; on peut accéder au JSON brut avant prétraitement avec -r ou raw=True
  • Il peut aussi être utilisé comme bibliothèque Python, ce qui permet de recevoir le résultat sous forme de dictionnaire Python, de liste de dictionnaires ou d’iterable paresseux au lieu de JSON
  • Pour les pipelines volumineux ou de longue durée, il propose des parseurs en streaming exportant en JSON Lines/NDJSON, ainsi que --slurp, --meta-out, des plugins de parseur locaux et des options pour supprimer les avertissements de compatibilité plateforme

Ce que fait JC

  • JC(JSON Convert) convertit en JSON la sortie de nombreux outils CLI, formats de fichiers et chaînes génériques
  • La sortie convertie peut ensuite être envoyée par pipe vers des outils comme jq ou jello pour un traitement supplémentaire
  • Un exemple est dig example.com | jc --dig, qui convertit la sortie de dig en tableau JSON, puis jq -r '.[].answer[].data' pour n’extraire que les adresses IP
  • La même opération peut aussi être exécutée avec la magic syntax, comme jc dig example.com | jq -r '.[].answer[].data'

Utilisation en CLI et comme bibliothèque Python

  • L’usage CLI de base consiste à recevoir une entrée via pipe et à produire une représentation JSON
    • COMMAND | jc [SLICE] [OPTIONS] PARSER
    • cat FILE | jc [SLICE] [OPTIONS] PARSER
    • echo STRING | jc [SLICE] [OPTIONS] PARSER
  • La magic syntax consiste à préfixer une commande ou un fichier /proc avec jc, sous la forme jc [SLICE] [OPTIONS] COMMAND ou jc [SLICE] [OPTIONS] /proc/<path-to-procfile>
    • Les alias de commande et les shell builtins ne sont pas pris en charge
  • En Python, on l’appelle par exemple avec jc.parse('dig', cmd_output)
    • La valeur de retour n’est pas une chaîne JSON mais peut être un dictionnaire Python, une liste de dictionnaires ou, pour les parseurs en streaming, un iterable paresseux
  • La documentation du package Python est disponible via help('jc'), help('jc.lib') ou dans la documentation en ligne

Représentation de la sortie et schéma

  • La représentation par défaut utilise un schéma strict propre à chaque parseur
    • Les nombres connus sont convertis en valeurs JSON int ou float
    • Les valeurs None connues sont converties en JSON null
    • Les valeurs booléennes connues sont également converties
    • Certains parseurs ajoutent des champs sémantiques supplémentaires
  • Le JSON brut avant prétraitement est accessible via l’option -r en CLI ou parse(..., raw=True) dans la bibliothèque Python
  • Le schéma de chaque parseur peut être consulté via les liens de documentation figurant à côté de la liste des parseurs
  • La sortie JSON est compacte par défaut, et l’option -p permet d’utiliser un pretty format
  • Une sortie YAML est aussi possible avec -y ou --yaml-out

Parseurs pris en charge et exemples d’usage

  • Les éléments pris en charge se répartissent entre commandes CLI, formats de fichiers et parseurs de chaînes
  • La liste des parseurs dans le README inclut notamment dig, ls, ping, ps, netstat, ifconfig, csv, xml, yaml, /etc/hosts, /etc/passwd, /proc/, systemctl, git log, jwt, url, semver et des fichiers liés à x509
  • Les exemples de sortie montrent comment divers types d’entrée sont transformés en structures JSON
    • La sortie de arp est convertie en champs adresse, type matériel, adresse MAC et interface
    • Un fichier CSV est converti en tableau d’objets utilisant les en-têtes comme clés
    • /etc/hosts est converti en adresses IP et tableaux de noms d’hôte
    • ifconfig est converti en objet contenant le nom de l’interface, la MTU, les adresses IPv4/IPv6, les compteurs de paquets et d’octets, etc.
    • ping est converti en objet contenant les nombres de paquets envoyés/reçus, le taux de perte, le temps aller-retour et la liste des réponses
  • Dans Ansible, il peut être utilisé comme filter plugin de la collection community.general

Installation

  • jc peut être installé de plusieurs façons
    • pip3 install jc
    • via les dépôts de paquets de l’OS
    • via des binaires par architecture sur GitHub Releases
  • Exemples d’installation via les paquets de l’OS
    • Debian/Ubuntu : apt-get install jc
    • Fedora : dnf install jc
    • openSUSE : zypper install jc
    • Arch : pacman -S jc
    • macOS : brew install jc
    • FreeBSD : installation via les ports
    • Filter plugin Ansible : ansible-galaxy collection install community.general
    • Connecteur FortiSOAR : installation depuis le FortiSOAR Connector Marketplace

Options principales

  • -a / --about : affiche des informations sur jc et ses parseurs en JSON ou YAML
  • -d / --debug : affiche des messages de trace en cas de problème de parsing ; -dd fournit un niveau de debug plus détaillé
  • -h / --help : affiche l’aide, et jc -h --parser_name permet de consulter la documentation d’un parseur
  • -M / --meta-out : ajoute à la sortie des métadonnées comme l’horodatage, le nom du parseur, la magic command et le code de sortie de la magic command
  • -q / --quiet : supprime les avertissements des parseurs ; -qq ignore les erreurs des parseurs en streaming
  • -s / --slurp : regroupe plusieurs lignes d’entrée dans un tableau
  • -u / --unbuffer : désactive la mise en tampon de la sortie
  • -B / --bash-comp, -Z / --zsh-comp : génère un script de complétion Bash ou Zsh

Slice et Slurp

  • Slice traite seulement une partie des lignes d’entrée avec une syntaxe START:STOP semblable au slicing Python
  • Par exemple, pour un tableau avec en-tête et pied de page, jc 1:-1 --asciitable ou jc 1:4 --asciitable permet de convertir uniquement les données intermédiaires en JSON
  • Les slices positives et les slices vides sont les plus économes en mémoire, tandis que les slices négatives utilisent davantage de mémoire
  • Slurp permet à des parseurs de chaînes acceptant une seule ligne d’entrée de produire en une fois un tableau contenant plusieurs éléments
    • Exemple : un fichier contenant une adresse IP par ligne peut être traité avec jc --slurp --ip-address
  • La magic syntax /proc prend automatiquement en charge le slurp quand plusieurs fichiers sont sélectionnés
    • Lors de la conversion de plusieurs fichiers /proc, un champ _file est ajouté à chaque objet résultat pour indiquer de quel fichier il provient
  • Si --meta-out est utilisé avec slurp, la sortie adopte une structure enveloppée avec un tableau result et un objet _jc_meta

Codes de sortie et métadonnées

  • Une erreur fatale interne à jc produit le code de sortie 100, sinon il renvoie 0
  • En cas d’usage de la magic syntax, jc conserve le code de sortie du programme analysé et l’ajoute à son propre code de sortie
    • Si ifconfig vaut 1 et jc vaut 0, le code combiné vaut 1
    • Si ifconfig vaut 0 et jc vaut 100, le code combiné vaut 100
    • Si les deux sont en erreur, le code combiné vaut 101
  • Avec --meta-out ou -M en magic syntax, l’objet _jc_meta inclut les informations sur la magic command et son code de sortie

Couleurs et variables d’environnement

  • La variable d’environnement JC_COLORS permet de définir les couleurs des key names, keywords, nombres et chaînes
    • Le format est JC_COLORS=<keyname_color>,<keyword_color>,<number_color>,<string_color>
    • Les couleurs possibles sont black, red, green, yellow, blue, magenta, cyan, gray, brightblack, brightred, brightgreen, brightyellow, brightblue, brightmagenta, brightcyan, white, default
  • Si la variable d’environnement NO_COLOR est définie, la sortie colorée est désactivée
  • L’option -C force la sortie colorée et est prioritaire sur la variable d’environnement NO_COLOR et sur l’option -m

Parseurs en streaming

  • La plupart des parseurs lisent l’intégralité de STDIN en mémoire avant de parser puis de sérialiser un document JSON en sortie
  • Certains parseurs en streaming traitent l’entrée ligne par ligne dès sa réception et produisent du JSON Lines ou du NDJSON
    • Exemples : ls-s, ping-s
    • Cela peut réduire fortement l’usage mémoire sur de grosses sorties de commandes comme ls -lR / et, dans certains cas, accélérer le traitement
  • Les parseurs en streaming ne peuvent pas être utilisés avec la magic syntax
  • Pour éviter qu’une erreur de parsing ne casse un pipeline de longue durée, on peut utiliser -qq ou ignore_exceptions=True en Python
    • Les lignes réussies incluent _jc_meta.success: true
    • Les lignes en échec incluent _jc_meta.success: false, error et line
  • Si la sortie tarde à apparaître entre deux pipes à cause des buffers du système d’exploitation, on peut utiliser -u pour une sortie non tamponnée
    • Toutefois, sur de gros flux de données, la sortie non tamponnée peut être plus lente
  • En Python, les parseurs en streaming acceptent une entrée iterable et renvoient un objet iterable, ce qui permet un traitement paresseux

Plugins de parseur

  • Les parser plugins locaux peuvent être placés dans le dossier jc/jcparsers du répertoire de données de l’application
    • Linux/unix : $HOME/.local/share/jc/jcparsers
    • macOS : $HOME/Library/Application Support/jc/jcparsers
    • Windows : $LOCALAPPDATA\jc\jc\jcparsers
  • Un plugin est un fichier de module Python standard
  • On peut utiliser jc/parsers/foo.py ou jc/parsers/foo_s.py comme modèle
  • Le nom de fichier du plugin doit être un nom de module Python valide : commencer par une lettre et ne contenir que des caractères alphanumériques et des underscores
  • Un plugin local peut remplacer un parseur par défaut

Locale, fuseau horaire et compatibilité

  • Pour de meilleurs résultats, il est recommandé de définir LC_ALL sur C ou en_US.UTF-8
  • Sur certains anciens systèmes, la locale C ne prend pas en charge l’encodage UTF-8, ce qui peut dégrader la sortie UTF-8 en ASCII avec des séquences d’échappement \\u
  • Certains parseurs ajoutent des champs d’horodatage epoch calculés
    • Si le nom du champ n’a pas le suffixe _utc, il est considéré comme un horodatage naïf basé sur le fuseau horaire local
    • Si le fuseau UTC est détecté dans le texte de sortie de la commande, l’horodatage devient timezone aware et le nom de clé reçoit le suffixe _utc
    • Les fuseaux autres que UTC ne sont pas pris en charge sous forme d’horodatage aware
  • Certains parseurs convertissent des sorties spécifiques à une plateforme et affichent un avertissement s’ils sont exécutés sur une plateforme non prise en charge
  • Même sur une plateforme non prise en charge, il reste possible de parser des fichiers de sortie générés sur d’autres systèmes ; dans ce cas, on peut supprimer l’avertissement avec -q ou quiet=True en Python
  • Les plateformes testées incluent Centos 7.7, Ubuntu 18.04/20.04, Fedora32, macOS 10.11.6/10.14.6, NixOS, FreeBSD12, Windows 10, Windows Server 2016 et Windows Server 2019

1 commentaires

 
GN⁺ 2023-12-10
Commentaires Hacker News
  • Sous FreeBSD, ce problème est en partie résolu avec libxo :
    on peut obtenir une sortie structurée comme avec $ ps --libxo=json | jq
    Ce n’est pas parfait : la prise en charge de ls existait, mais a été supprimée pour une raison quelconque, et tous les utilitaires ne la prennent pas en charge
    jc ressemble à une excellente solution temporaire, avec de nombreux parseurs de commandes, mais elle a la limite de parser une sortie texte qui n’a pas été conçue à l’origine pour cela
    Ce serait bien que les utilitaires convergent vers une sortie structurée via un flag commun ; même si aller jusqu’au comportement par défaut de PowerShell serait excessif pour Unix/Linux, un simple flag standard --json serait déjà une grande avancée
    https://wiki.freebsd.org/LibXo
    https://reviews.freebsd.org/D13959

    • PowerShell va encore un cran plus loin que JSON en prenant en charge de véritables objets modifiables
      Si je comprends bien, il ne se contente pas de transmettre des données structurées : il fait circuler des objets opaques dans le pipeline, et l’on peut même revenir à l’étape précédente pour appeler des méthodes : https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_methods?view=powershell-7.4
      J’aime bien les wrappers comme jc, libxo, ainsi que les shells expérimentaux comme https://www.nushell.sh/, mais ils se concentrent davantage sur la transmission de données que sur des objets dotés de méthodes exécutables
      Les données structurées restent très « Unix » dans l’esprit, et si l’on a besoin de vrais objets, c’est probablement le moment de lancer Python ou Ruby
      Aussi excellent qu’un shell puisse être, et même s’il offre de bonnes fonctionnalités de programmation, il est important de savoir quand passer d’un script shell à un vrai langage de programmation
    • Un billet de blog de l’auteur original traite justement de ce point
      jc y est présenté comme un outil qui comble ce manque pour l’instant, et semble conçu comme un tremplin en attendant que la prise en charge de -j/--json se généralise dans les outils Unix
      https://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/
    • Dans SerenityOS aussi, de façon similaire, les éléments sous /proc renvoient des données JSON plutôt que des fichiers texte non structurés
      Une solution plus structurelle pourrait consister à permettre l’export de structures de données depuis ELF, à sérialiser ces structures vers une sortie terminal, puis à les afficher ou les traiter dans le format souhaité par l’utilisateur, comme JSON ou YAML
    • Libxo est séduisant en théorie, mais il semble que, plutôt que de transmettre une structure à libxo et de lui confier le formatage, l’application doive implémenter elle-même la logique pour chaque format de sortie
      Je ne me souviens plus de l’utilitaire exact, mais c’était probablement iostat : il formatait les lignes de sortie JSON par interpolation de chaînes, et certaines combinaisons de flags produisaient une sortie complètement cassée
      Je ne sais pas si cela s’est amélioré depuis, mais avec une option d’intervalle, je m’attendais à quelque chose du type JSON Lines
      En matière d’utilisabilité, je trouve que PowerShell et kubectl ont une nette longueur d’avance sur libxo
    • Libxo est inclus dans le système de base de FreeBSD, mais il peut aussi être utilisé plus largement
      https://github.com/Juniper/libxo
      https://libxo.readthedocs.io/en/latest/
  • L’idée est vraiment bonne, mais quand on pense à la maintenance, cela rend inquiet
    Entre les versions, les variations de sortie selon les flags des commandes, etc., cela ressemble à un enfer à maintenir ; en pratique, cela fonctionnera sans doute bien dans certains cas, mais au-delà des scénarios de base, l’effet de nouveauté risque de s’estomper rapidement
    En plus, utiliser -- pour les options de l’outil ne me semble pas idéal
    Si chaque nouveau parseur nécessite de nouveaux flags, l’aide ou les pages de manuel pourraient atteindre des milliers de lignes

    • Vu comme un shim dont le périmètre se réduira à mesure que les utilitaires en ligne de commande prendront de plus en plus en charge la sortie JSON, cela me paraît acceptable
      Lorsqu’un utilitaire décide de proposer son propre export JSON, cet outil n’a plus qu’à déléguer à cette fonctionnalité
    • En lisant un peu plus bas dans la documentation, on voit qu’on peut aussi l’utiliser en préfixant la commande avec jc, comme jc ls
      Le paramètre --cmd permet de traiter les données avant la conversion, ce qui est plutôt une bonne idée
      Par exemple, on peut vouloir faire un grep sur la liste avant de la convertir
      Côté maintenance, la sortie des commandes Unix de base ne change pas énormément : si elle changeait, cela casserait non seulement cet outil, mais aussi d’innombrables scripts, donc les mises à jour d’autres binaires ne devraient pas le casser aussi souvent qu’on pourrait le penser
    • D’un autre côté, je suis partagé
      Un ensemble commun de parseurs bien maintenu vaut mieux que des parseurs ad hoc dispersés un peu partout, mais lorsqu’on a besoin d’une sortie JSON native pour faire des choses complexes, j’aimerais que le parsing n’ajoute pas lui-même des problèmes
      Pour l’utiliser confortablement, il faudra sans doute que j’envoie des PR avec des cas de test supplémentaires pour tout ce que je compte utiliser
      De toute façon, ces tests, j’aurais dû les écrire moi-même
    • Cela demande de la collaboration
      Il faut que les gens soumettent les informations de parsing pour les outils dont ils ont besoin, et que les utilisateurs puissent facilement rester à jour
    • C’est l’un des meilleurs usages possibles des LLM, qui ont montré de bonnes capacités à transformer du texte non structuré en objets structurés
  • Nushell adopte une approche différente, mais arrive pour l’essentiel au même point : des données structurées pour les commandes du shell
    C’est principalement le shell lui-même qui prend ce rôle en charge
    http://www.nushell.sh/

    • Nushell dispose d’une opération from json, ce qui le rend en pratique très complémentaire de JC
      J’avais enregistré une vidéo montrant quelques bonnes fonctionnalités de Nushell, et vers 19 minutes je parle de son association avec jc : https://www.youtube.com/watch?v=KF5dtxVsn1E
    • J’avais jeté un œil à Nushell de temps en temps depuis sa première annonce, mais ce n’est qu’il y a un mois ou deux que j’en ai enfin vraiment compris l’essentiel
      J’essayais d’écrire un script pour parcourir des fichiers dans des répertoires correspondant à un certain motif, puis supprimer ceux dont les dates de modification étaient à moins de 10 minutes les unes des autres, et je me suis souvenu que Nushell était bien adapté à ce genre de tâche
      Après l’avoir essayé un peu, j’ai enfin eu le déclic, et maintenant je suis accro
      Même avec des données non structurées, pouvoir les convertir et les traiter sous une forme comme une liste d’enregistrements donne beaucoup de puissance
  • Dans un certain sens, le fait qu’il y ait des fichiers partout est excellent, c’est la promesse d’Unix, et Plan 9 pousse cela encore plus loin
    Mais le fait qu’il s’agisse de fichiers non structurés, ou que chaque fichier ait son propre format, est tout aussi handicapant
    Même parser un seul fichier de logs nginx avec seulement des outils comme awk peut devenir pénible
    L’un des gros inconvénients est qu’il est difficile de mener de grandes réécritures ou refontes de conception dans l’espace utilisateur Linux
    Je veux un shell plus intelligent, j’aime aussi les fichiers, et j’ai toujours un livre sur awk à portée de main, mais je pense qu’il est temps d’améliorer sérieusement le parsing des données
    De la même manière qu’un programme sait décider s’il doit rendre une sortie colorée ou non, ce serait bien qu’il puisse aussi décider s’il doit émettre une sortie structurée

    • Une partie du problème est que la sortie des commandes est à la fois une interface utilisateur et une API
      Comme n’importe quelle UI textuelle peut être utilisée comme une API, le texte facile à lire pour les humains passe en priorité
      Le scripting shell ressemble donc à l’écriture d’extensions de navigateur tierces
      On observe, on devine, puis on bricole un parseur à partir de ces suppositions en espérant que ça marche
      Ce serait bien d’avoir une troisième sortie standard dédiée au contenu lisible par les machines
      Le terminal ne l’afficherait pas par défaut, mais lors d’un pipe ce serait cette sortie qui serait transmise ; elle devrait être en JSONL, et les pages de manuel préciseraient le contrat
      Ainsi, la sortie standard resterait destinée aux humains, et le parsing deviendrait quelque chose qu’on fait en sachant que c’est possible mais fragile
      Bien sûr, c’est une idée qui casserait complètement la compatibilité descendante, et si l’on modernisait les CLI de façon irréaliste en les réinventant depuis leurs fondations, la liste des choses à changer serait longue
    • Il y a un demi-siècle, de très bonnes idées sont apparues, et c’est frustrant de voir tant de gens les prendre comme un évangile plutôt que comme quelque chose à améliorer
      Obtenir des améliorations est aussi vraiment difficile, et quand quelqu’un comme Lennart essaie de retirer de vieilles scories vieilles de plusieurs décennies, le drame éclate immédiatement
      JSON n’est pas encore parfait non plus
      JSON est une idée à peu près correcte, mais pour bien faire les choses, il faudrait un format capable d’exposer des choses comme les types de données
      Il faudrait quelque chose de plus proche de PowerShell, afin de traiter les nombres comme des nombres et de pouvoir faire des choses étonnantes comme calculer un écart entre dates avec $a - $b
    • Une amélioration plus simple serait d’implémenter une sortie JSON via un argument de ligne de commande dans tous les outils CLI
      Ce serait bien que cela arrive dans GNU coreutils
    • Il reste toujours l’option PowerShell
      Le problème est qu’il est tellement enraciné dans .NET et les objets qu’il est très difficile de l’intégrer aux commandes natives existantes, quelle que soit la plateforme
    • Pour le parsing des logs serveur, c’est dommage qu’on ne puisse pas extraire cette fonctionnalité d’outils comme logstash
      Parce qu’ils font déjà essentiellement la même chose
      Mais l’objectif final sera sans doute que les outils de plus haut niveau reconnaissent cette valeur et fournissent eux-mêmes une sortie structurée
  • Excellent
    Je soutiens fortement les outils CLI qui proposent une sortie JSON raisonnable, et des choses comme https://github.com/WireGuard/wireguard-tools/blob/master/contrib/json/wg-json ainsi que le |ConvertTo-Json de PowerShell jouent un grand rôle dans l’automatisation de l’administration et de la supervision
    Cela dit, ici, raisonnable fait beaucoup de travail, et la réalité reste la réalité
    Comme avec la façon dont LSI/Broadcom StorCLI ajoute un J derrière les commandes, ou les wrappers de PowerShell qui masquent COM : techniquement, c’est du JSON, mais le résultat est absurdement complexe ou inutile, si bien qu’on revient souvent à l’expédient « faisons juste passer quelques regex sur la sortie en texte brut »
    Je compte quand même absolument regarder ça
    Si le premier exemple, le parsing de la sortie de dig, représente ce qu’on peut faire de manière fiable, cela pourrait être assez intéressant

  • Je pense que jc dig example.com devrait être la syntaxe par défaut
    dig example.com | jc --dig doit en effet deviner après coup les options et paramètres de la commande précédente pour parser sa sortie

  • Le fait que toutes les sorties soient des objets est l’un des aspects que je préfère dans PowerShell
    Ça me manque chaque fois que je dois écrire un script bash

  • Respect à la personne qui s’est chargée de maintenir ça

    • Respect aussi à la personne qui a décidé d’utiliser cet outil en se confrontant réellement aux hypothèses erronées qu’il peut faire
    • Je me demande comment les problèmes de version seront gérés
      Un truc comme aws s3 ls | jc --aws=1.2.3, ce serait un cauchemar
    • Bonne remarque
      Ça me fait penser au programme file de Linux ou Unix, et aux héros qui le maintiennent
    • En théorie, s’il pouvait charger quelque chose comme des plugins, par exemple sous la forme de commandes shell séparées, une partie de l’effort de maintenance pourrait être transférée aux auteurs de plugins
  • Je me demande s’il existe une liste d’outils en ligne de commande Unix modernes acceptant l’option --json
    Il pourrait être utile d’ajouter ce type d’information à ce dépôt

    • Sous FreeBSD, pratiquement tout est pris en charge via libxo
    • Il n’y a pas de liste, mais ip, le remplaçant moderne de ifconfig, prend en charge JSON
      lldpctl le prend aussi en charge
      Ansible fournit les détails du système sous forme de JSON appelés facts, et l’automatisation est conçue pour les exploiter
    • lsblk accepte le flag --json et peut fournir beaucoup d’informations
      On peut essayer lsblk --json --output-all
      C’est très utile lorsqu’un script doit vérifier les disques et partitions du système
    • TShark, l’outil CLI compagnon de Wireshark, le prend en charge avec le flag -T json
    • Ce n’est peut-être pas la réponse attendue, mais il y a l’AWS CLI
  • Projet intéressant
    Mais je pensais qu’un outil comme textfsm serait utilisé comme parseur de première étape
    textfsm est beaucoup utilisé pour analyser les sorties CLI des équipements réseau
    https://github.com/google/textfsm