JC, converte a saída de ferramentas populares de linha de comando em JSON
(github.com/kellyjonbrazil)- JC é uma ferramenta que converte a saída de várias ferramentas CLI, formatos de arquivo e strings comuns em JSON, facilitando o parsing em scripts
- Recebe entrada via pipe e envia JSON para
STDOUT, além de oferecer suporte à magic syntax ao ser prefixado a um comando, como emjc dig example.com - A saída padrão usa um schema estrito por parser e converte números,
null, booleanos e campos semânticos adicionais; é possível acessar o JSON bruto antes do pré-processamento com-rouraw=True - Também pode ser usado como biblioteca Python, retornando o resultado como dicionário Python, lista de dicionários ou iterable preguiçoso em vez de JSON
- Para pipelines grandes ou de longa duração, oferece streaming parsers que produzem JSON Lines/NDJSON, além de
--slurp,--meta-out, plugins locais de parser e opções para suprimir avisos de compatibilidade de plataforma
O que o JC faz
- JC (JSON Convert) converte a saída de várias ferramentas CLI, formatos de arquivo e strings comuns em JSON
- A saída convertida pode ser encadeada via pipe com ferramentas como
jqoujellopara processamento adicional - Um exemplo é
dig example.com | jc --dig, que converte a saída dedigem um array JSON, seguido dejq -r '.[].answer[].data'para extrair apenas os endereços IP - O mesmo trabalho também pode ser executado com magic syntax, como em
jc dig example.com | jq -r '.[].answer[].data'
Como usar via CLI e biblioteca Python
- O uso básico na CLI recebe entrada por pipe e gera uma representação em JSON
COMMAND | jc [SLICE] [OPTIONS] PARSERcat FILE | jc [SLICE] [OPTIONS] PARSERecho STRING | jc [SLICE] [OPTIONS] PARSER
- A magic syntax usa o formato
jc [SLICE] [OPTIONS] COMMANDoujc [SLICE] [OPTIONS] /proc/<path-to-procfile>, prefixandojcao comando ou arquivo em/proc- aliases de comando e shell builtins não são suportados
- Em Python, pode ser chamado como
jc.parse('dig', cmd_output)- O valor retornado não é uma string JSON, mas pode ser um dicionário Python, uma lista de dicionários ou um iterable preguiçoso no caso de streaming parsers
- A documentação do pacote Python pode ser consultada com
help('jc'),help('jc.lib')ou na documentação online
Representação de saída e schema
- A representação padrão usa um schema estrito específico para cada parser
- Números conhecidos são convertidos para valores JSON
intoufloat - Valores
Noneconhecidos são convertidos para JSONnull - Valores booleanos conhecidos também são convertidos
- Alguns parsers adicionam campos semânticos extras
- Números conhecidos são convertidos para valores JSON
- O JSON bruto antes do pré-processamento pode ser acessado com a opção
-rna CLI ouparse(..., raw=True)na biblioteca Python - O schema de cada parser pode ser visto nos links de documentação ao lado da lista de parsers
- A saída JSON é compacta por padrão, e a opção
-ppermite usar pretty format - Também é possível gerar saída em YAML com
-you--yaml-out
Parsers suportados e exemplos de uso
- O suporte inclui comandos CLI, formatos de arquivo e parsers de string
- A lista de parsers no README inclui itens como
dig,ls,ping,ps,netstat,ifconfig,csv,xml,yaml,/etc/hosts,/etc/passwd,/proc/,systemctl,git log,jwt,url,semvere arquivos relacionados ax509 - As saídas de exemplo mostram como várias entradas são convertidas em estruturas JSON
- A saída de
arpé convertida em campos como endereço, tipo de hardware, endereço MAC e interface - Arquivos CSV são convertidos em arrays de objetos com os cabeçalhos como chaves
/etc/hostsé convertido em IP e arrays de hostnameifconfigé convertido em objetos contendo nome da interface, MTU, endereços IPv4/IPv6, contadores de pacotes e bytes etc.pingé convertido em objetos com contagem de pacotes enviados e recebidos, taxa de perda, tempo de ida e volta e lista de respostas
- A saída de
- No Ansible, pode ser usado como filter plugin da coleção
community.general
Como instalar
jcpode ser instalado de várias formaspip3 install jc- repositórios de pacotes do sistema operacional
- binários por arquitetura no GitHub Releases
- Exemplos de instalação por pacote de sistema incluem:
- Debian/Ubuntu:
apt-get install jc - Fedora:
dnf install jc - openSUSE:
zypper install jc - Arch:
pacman -S jc - macOS:
brew install jc - FreeBSD: instalação via ports
- Ansible filter plugin:
ansible-galaxy collection install community.general - FortiSOAR connector: instalação pelo FortiSOAR Connector Marketplace
- Debian/Ubuntu:
Principais opções
-a/--about: mostra informações sobrejce o parser em JSON ou YAML-d/--debug: exibe mensagens de rastreamento quando há problemas de parsing;-ddfornece depuração mais detalhada-h/--help: mostra a ajuda, ejc -h --parser_nameexibe a documentação do parser-M/--meta-out: adiciona à saída metadados como timestamp, nome do parser, magic command e código de saída do magic command-q/--quiet: suprime avisos do parser;-qqignora erros de streaming parsers-s/--slurp: agrupa entradas de múltiplas linhas em um array-u/--unbuffer: desativa o buffering da saída-B/--bash-comp,-Z/--zsh-comp: gera scripts de completion para Bash ou Zsh
Slice e Slurp
- Slice processa apenas parte das linhas de entrada com a sintaxe
START:STOP, semelhante ao slicing do Python - Por exemplo, em uma tabela com cabeçalho e rodapé, é possível converter apenas os dados centrais em JSON com
jc 1:-1 --asciitableoujc 1:4 --asciitable - Slices positivos e slices vazios são os mais eficientes em memória, enquanto slices negativos usam mais memória
- Slurp é um recurso para parsers de string que recebem uma única linha, permitindo emitir múltiplos itens de uma vez em um array
- Exemplo: processar um arquivo com vários endereços IP, um por linha, usando
jc --slurp --ip-address
- Exemplo: processar um arquivo com vários endereços IP, um por linha, usando
- A magic syntax de
/procoferece suporte automático a slurp ao selecionar múltiplos arquivos- Ao converter vários arquivos de
/proc, um campo_fileé adicionado para indicar a qual arquivo cada objeto de resultado corresponde
- Ao converter vários arquivos de
- Ao usar
--meta-outcom slurp, a saída vem envolvida em uma estrutura com arrayresulte objeto_jc_meta
Códigos de saída e metadados
- Erros fatais internos do
jcgeram código de saída100; caso contrário, retorna0 - Ao usar magic syntax, o
jcarmazena o código de saída do programa analisado e o soma ao código de saída do própriojc- Se
ifconfigretornar1ejcretornar0, o código combinado será1 - Se
ifconfigretornar0ejcretornar100, o código combinado será100 - Se ambos tiverem erro, o código combinado será
101
- Se
- Em magic syntax, ao usar
--meta-outou-M, o objeto_jc_metainclui informações do magic command e o código de saída
Cores e variáveis de ambiente
- A variável de ambiente
JC_COLORSpermite definir as cores de key name, keyword, number e string- O formato é
JC_COLORS=<keyname_color>,<keyword_color>,<number_color>,<string_color> - Os valores possíveis são
black,red,green,yellow,blue,magenta,cyan,gray,brightblack,brightred,brightgreen,brightyellow,brightblue,brightmagenta,brightcyan,white,default
- O formato é
- Ao definir a variável de ambiente
NO_COLOR, a saída colorida é desativada - A opção
-Ctem prioridade sobre a variávelNO_COLORe sobre a opção-m, forçando a saída com cores
Streaming parsers
- A maioria dos parsers lê toda a
STDINpara a memória, faz o parsing e então serializa a saída como documento JSON - Alguns streaming parsers processam a entrada linha por linha assim que a recebem e produzem JSON Lines ou NDJSON
- Exemplos:
ls-s,ping-s - Isso pode reduzir bastante o uso de memória em saídas grandes, como
ls -lR /, e em alguns casos também acelerar o processamento
- Exemplos:
- Streaming parsers não podem ser usados com magic syntax
- Para evitar que erros de parsing quebrem o pipe em pipelines de longa duração, use
-qqouignore_exceptions=Trueem Python- Linhas bem-sucedidas incluem
_jc_meta.success: true - Linhas com falha incluem
_jc_meta.success: false,erroreline
- Linhas bem-sucedidas incluem
- Se a saída demorar a aparecer entre pipes por causa do buffer do sistema operacional, use
-upara saída sem buffer- Porém, em streams grandes, a saída sem buffer pode ser mais lenta
- Em Python, streaming parsers recebem entradas iteráveis e retornam objetos iteráveis, permitindo processamento preguiçoso
Plugins de parser
- Um parser plugin local pode ser colocado na pasta
jc/jcparsersdo diretório de dados do aplicativo- Linux/unix:
$HOME/.local/share/jc/jcparsers - macOS:
$HOME/Library/Application Support/jc/jcparsers - Windows:
$LOCALAPPDATA\jc\jc\jcparsers
- Linux/unix:
- O plugin é um arquivo de módulo Python padrão
jc/parsers/foo.pyoujc/parsers/foo_s.pypodem ser usados como template- O nome do arquivo do plugin deve ser um nome de módulo Python válido, começar com letra e conter apenas caracteres alfanuméricos e underscore
- Plugins locais podem sobrescrever o parser padrão
Localidade, fuso horário e compatibilidade
- Para melhores resultados, recomenda-se definir
LC_ALLcomoCouen_US.UTF-8 - Em alguns sistemas antigos, o locale
Cnão oferece suporte à codificação UTF-8, o que pode degradar a saída UTF-8 para ASCII com sequências de escape\\u - Alguns parsers adicionam campos calculados de timestamp epoch
- Se o nome do campo não tiver o sufixo
_utc, ele é tratado como timestamp ingênuo baseado no fuso horário local - Se o fuso UTC for detectado no texto da saída do comando, o timestamp será timezone-aware e a chave receberá o sufixo
_utc - Fusos diferentes de UTC não são suportados como timestamps aware
- Se o nome do campo não tiver o sufixo
- Alguns parsers convertem saídas específicas de plataforma, então ao executá-los em plataformas não suportadas um aviso é exibido
- Mesmo em plataformas não suportadas, ainda é possível fazer parsing de arquivos de saída gerados em outro sistema; nesse caso, o aviso pode ser suprimido com
-qouquiet=Trueem Python - As plataformas testadas incluem Centos 7.7, Ubuntu 18.04/20.04, Fedora32, macOS 10.11.6/10.14.6, NixOS, FreeBSD12, Windows 10, Windows Server 2016 e Windows Server 2019
1 comentários
Comentários do Hacker News
No FreeBSD, esse problema já foi resolvido em certa medida com a libxo:
É possível obter saída estruturada, como em
$ ps --libxo=json | jqNão é perfeito: havia suporte a
ls, mas ele foi removido por algum motivo, e nem todos os utilitários o suportamO
jcparece uma excelente solução temporária, oferecendo parsers para muitos comandos, mas tem a limitação de fazer parsing de saída de texto que, para começo de conversa, não foi projetada para issoSeria bom se os utilitários convergissem para emitir saída estruturada por meio de uma flag comum; mesmo que tornar isso o padrão, como no PowerShell, seja exagero para Unix/Linux, só uma flag
--jsonpadrão já seria um grande avançohttps://wiki.freebsd.org/LibXo
https://reviews.freebsd.org/D13959
Pelo que entendo, ele não apenas passa dados estruturados adiante, mas deixa objetos opacos fluírem pelo pipeline e permite até voltar a etapas anteriores para chamar métodos: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_methods?view=powershell-7.4
Gosto de wrappers como
jcelibxo, e também de shells experimentais como https://www.nushell.sh/, mas eles se concentram mais em transmitir dados do que em objetos com métodos executáveisDados estruturados ainda parecem algo no estilo Unix; se você precisa de objetos de verdade, acho que é hora de abrir Python ou Ruby
Por melhor que um shell seja, e por melhores que sejam seus recursos de programação, é importante saber quando passar de um script de shell para uma linguagem de programação de verdade
O
jcé apresentado como uma ferramenta que vem preenchendo esse papel, e dá para entender que ele foi pensado como uma ponte até que o suporte a-j/--jsonse espalhe amplamente pelas ferramentas Unixhttps://blog.kellybrazil.com/2019/11/26/bringing-the-unix-philosophy-to-the-21st-century/
/procretornam dados JSON em vez de arquivos de texto não estruturadoUma solução mais estrutural poderia ser permitir que estruturas de dados fossem exportadas a partir de ELF, serializar essas estruturas para a saída do terminal e então fazer com que o usuário as emitisse ou processasse no formato desejado, como JSON ou YAML
Não lembro exatamente qual utilitário era, talvez
iostat, mas ele formatava linhas de saída JSON por interpolação de strings e, com uma certa combinação de flags, gerava uma saída completamente quebradaNão sei se melhorou hoje, mas quando há uma opção de intervalo eu esperaria algo como JSON lines
Em termos de usabilidade, acho que PowerShell e kubectl estão muito à frente da libxo
https://github.com/Juniper/libxo
https://libxo.readthedocs.io/en/latest/
A ideia é muito boa, mas fico apreensivo pensando em manutenção
Considerando versões, mudanças de saída dependendo das flags dos comandos etc., parece um inferno de manutenção; na prática, deve funcionar bem em alguns casos, mas a novidade provavelmente se desgasta rápido quando se vai além dos casos básicos
Além disso, usar
--nas opções da ferramenta também não parece bomSe cada novo parser precisar de uma nova flag, a ajuda ou as páginas de manual podem acabar com milhares de linhas
Quando algum utilitário decidir oferecer sua própria exportação em JSON, a partir daí esta ferramenta pode simplesmente delegar para essa funcionalidade
jc, como emjc lsO parâmetro
--cmdpermite processar os dados antes da conversão, então é uma boa ideiaPor exemplo, você pode querer aplicar
grepà lista antes de convertê-laTambém do ponto de vista de manutenção, a saída dos comandos Unix básicos, se mudasse muito, quebraria não só esta ferramenta como inúmeros scripts; por isso, não acho que vá quebrar com tanta frequência quanto se imagina por causa de atualizações de outros binários
Um único conjunto comum de parsers bem mantido é melhor do que parsers improvisados de saída espalhados por aí, mas, em situações complexas em que eu precisaria de saída JSON direta, eu preferiria que o parsing em si não acrescentasse problemas
Para usar isso com tranquilidade, no fim eu provavelmente teria que enviar PRs com casos de teste adicionais para tudo que eu pretendesse usar
De todo modo, eu teria que escrever esses testes por conta própria
A única saída é as pessoas enviarem informações de parsing das ferramentas de que precisam, e quem usa conseguir manter tudo atualizado com facilidade
O Nushell segue uma abordagem diferente, mas acaba chegando em grande parte ao mesmo ponto: dados estruturados de comandos do shell
Principalmente de uma forma em que o próprio shell assume esse papel
http://www.nushell.sh/
from json, então ele combina muito bem com o JC na práticaHá um tempo gravei um vídeo mostrando alguns bons recursos do Nushell e, por volta dos 19 minutos, falo sobre combiná-lo com
jc: https://www.youtube.com/watch?v=KF5dtxVsn1EEu estava tentando escrever um script para varrer arquivos dentro de diretórios que correspondessem a um certo padrão e remover aqueles cujos horários de modificação estivessem a menos de 10 minutos entre si, e me lembrei de que o Nushell era bom para esse tipo de coisa
Mexi nele por um instante, finalmente caiu a ficha, e agora estou viciado
Mesmo dados não estruturados ficam poderosos se puderem ser convertidos e processados em uma forma como lista de registros
Em certo sentido, é excelente que arquivos existam em todo lugar; essa é a promessa do Unix e, no Plan 9, isso aparece de forma ainda mais expandida
Mas o fato de eles serem arquivos não estruturados, ou de cada arquivo ter seu próprio formato, também atrapalha na mesma medida
Até tentar analisar um único arquivo de log do nginx só com algo como
awkpode ser trabalhosoUma das grandes desvantagens é que é difícil executar reescritas de sistemas em larga escala ou mudanças de design no espaço de usuário do Linux
Quero um shell mais inteligente, gosto de arquivos e também tenho um livro de
awkali do lado, mas acho que já passou da hora de uma melhoria séria em análise de dadosSeria bom se programas pudessem decidir emitir uma saída estruturada ou não, assim como já decidem se devem renderizar saída colorida
Como qualquer UI textual pode ser usada como API, o texto fácil de ler para humanos acaba ganhando prioridade
Por isso, scripting em shell parece criar uma extensão de navegador de terceiros
Você observa e adivinha, improvisa um parser com base nessas suposições e torce para funcionar
Seria bom ter uma terceira saída padrão para conteúdo legível por máquina
O terminal não a exibiria por padrão; ao usar pipe, essa saída seria encaminhada; ela deveria ser JSONL; e as man pages especificariam o contrato
Assim, a saída padrão poderia continuar sendo para humanos, e o parsing passaria a ser algo feito sabendo que é possível, mas frágil
Claro que essa é uma ideia que quebra totalmente a compatibilidade retroativa; se fôssemos modernizar o CLI de forma irrealista, reinventando-o desde a base, haveria uma longa lista de coisas que eu gostaria de mudar
Conseguir melhorias também é realmente difícil, e quando alguém como Lennart tenta remover velhas sobras de décadas, o drama começa imediatamente
JSON também ainda não é perfeito
JSON é uma ideia razoavelmente boa, mas, para fazer direito, seria necessário um formato capaz de expor coisas como tipos de dados
Teria de ser algo mais próximo do PowerShell, para tratar números como números e fazer coisas surpreendentes como calcular a diferença entre datas com
$a - $bSeria bom ver isso acontecer no GNU coreutils
O problema é que ele está profundamente enraizado em .NET e objetos, o que torna muito difícil integrá-lo com comandos nativos existentes em qualquer plataforma
Porque ele já está essencialmente fazendo a mesma coisa
Ainda assim, o objetivo final provavelmente é que as ferramentas de nível superior reconheçam o valor disso e forneçam diretamente saída estruturada
Legal
Apoio fortemente ferramentas CLI que ofereçam uma saída JSON razoável, e coisas como https://github.com/WireGuard/wireguard-tools/blob/master/contrib/json/wg-json e o
|ConvertTo-Jsondo PowerShell representam uma grande parte da automação de administração/monitoramentoMas aqui razoável carrega muito peso, e a realidade é a realidade
Como no jeito da LSI/Broadcom StorCLI de acrescentar
Jdepois do comando, ou nos wrappers do PowerShell que escondem COM: tecnicamente é JSON, mas o resultado é absurdamente complexo ou inútil, então muitas vezes se acaba voltando ao remendo de “vamos só aplicar umas regexes na saída em texto puro”Ainda assim, pretendo dar uma olhada nisso
Se o primeiro exemplo, o parsing da saída do
dig, representar algo que dá para fazer de forma estável, parece bem interessanteAcho que
jc dig example.comdeveria ser a sintaxe padrãoPorque
dig example.com | jc --digexige adivinhar depois os flags e parâmetros do comando anterior para poder fazer o parsing da saídaO fato de toda saída ser um objeto é uma das minhas partes favoritas do PowerShell
Sinto falta disso sempre que preciso escrever scripts em bash
Respeito a quem decidiu manter isso
Algo como
aws s3 ls | jc --aws=1.2.3seria um pesadeloIsso me lembra o programa
filedo Linux ou Unix e os heróis que o mantêmplugins, por exemplo deixando-os como comandos de shell separados, daria para transferir parte do esforço de manutenção para os autores de pluginsFico curioso se há uma lista de ferramentas modernas de linha de comando Unix que aceitam a opção
--jsonTambém poderia ser útil adicionar esse tipo de informação a este repositório
ip, substituto moderno doifconfig, tem suporte a JSONlldpctltambém tem suporteO Ansible fornece detalhes do sistema como JSON chamado
facts, e foi projetado para que a automação use issolsblkaceita a flag--jsone pode fornecer muitas informaçõesBasta tentar
lsblk --json --output-allÉ muito útil quando um script precisa verificar os discos e partições do sistema
-T jsonProjeto interessante
Mas imaginei que usariam algo como textfsm como parser de primeira etapa
O textfsm é muito usado para fazer parsing da saída de CLI de equipamentos de rede
https://github.com/google/textfsm