awk / sed / perl one liner + como imprimir apenas as linhas de propriedades do arquivo json

10

como imprimir apenas as linhas de propriedades do arquivo json

exemplo de arquivo json

{
  "href" : "http://master02:8080/api/v1/clusters/HDP/configurations?type=kafka-env&tag=version1527250007610",
  "items" : [
    {
      "href" : "http://master02:8080/api/v1/clusters/HDP/configurations?type=kafka-env&tag=version1527250007610",
      "tag" : "version1527250007610",
      "type" : "kafka-env",
      "version" : 8,
      "Config" : {
        "cluster_name" : "HDP",
        "stack_id" : "HDP-2.6"
      },
      "properties" : {
        "content" : "\n#!/bin/bash\n\n# Set KAFKA specific environment variables here.\n\n# The java implementation to use.\nexport JAVA_HOME={{java64_home}}\nexport PATH=$PATH:$JAVA_HOME/bin\nexport PID_DIR={{kafka_pid_dir}}\nexport LOG_DIR={{kafka_log_dir}}\nexport KAFKA_KERBEROS_PARAMS={{kafka_kerberos_params}}\nexport JMX_PORT=9997\n# Add kafka sink to classpath and related depenencies\nif [ -e \"/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\" ]; then\n  export CLASSPATH=$CLASSPATH:/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\n  export CLASSPATH=$CLASSPATH:/usr/lib/ambari-metrics-kafka-sink/lib/*\nfi\n\nif [ -f /etc/kafka/conf/kafka-ranger-env.sh ]; then\n. /etc/kafka/conf/kafka-ranger-env.sh\nfi",
        "is_supported_kafka_ranger" : "true",
        "kafka_log_dir" : "/var/log/kafka",
        "kafka_pid_dir" : "/var/run/kafka",
        "kafka_user" : "kafka",
        "kafka_user_nofile_limit" : "128000",
        "kafka_user_nproc_limit" : "65536"
      }
    }
  ]

resultado esperado

    "content" : "\n#!/bin/bash\n\n# Set KAFKA specific environment variables here.\n\n# The java implementation to use.\nexport JAVA_HOME={{java64_home}}\nexport PATH=$PATH:$JAVA_HOME/bin\nexport PID_DIR={{kafka_pid_dir}}\nexport LOG_DIR={{kafka_log_dir}}\nexport KAFKA_KERBEROS_PARAMS={{kafka_kerberos_params}}\nexport JMX_PORT=9997\n# Add kafka sink to classpath and related depenencies\nif [ -e \"/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\" ]; then\n  export CLASSPATH=$CLASSPATH:/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\n  export CLASSPATH=$CLASSPATH:/usr/lib/ambari-metrics-kafka-sink/lib/*\nfi\n\nif [ -f /etc/kafka/conf/kafka-ranger-env.sh ]; then\n. /etc/kafka/conf/kafka-ranger-env.sh\nfi",
    "is_supported_kafka_ranger" : "true",
    "kafka_log_dir" : "/var/log/kafka",
    "kafka_pid_dir" : "/var/run/kafka",
    "kafka_user" : "kafka",
    "kafka_user_nofile_limit" : "128000",
    "kafka_user_nproc_limit" : "65536"
yael
fonte
3
Pergunta relacionada no SO: Analisando JSON com ferramentas Unix
hoefling
Também relacionado: stackoverflow.com/questions/1732348/…
Kartik 28/05

Respostas:

33

Jq é a ferramenta certa para processar dados JSON:

jq '.items[].properties | to_entries[] | "\(.key) : \(.value)"' input.json

A saída:

"content : \n#!/bin/bash\n\n# Set KAFKA specific environment variables here.\n\n# The java implementation to use.\nexport JAVA_HOME={{java64_home}}\nexport PATH=$PATH:$JAVA_HOME/bin\nexport PID_DIR={{kafka_pid_dir}}\nexport LOG_DIR={{kafka_log_dir}}\nexport KAFKA_KERBEROS_PARAMS={{kafka_kerberos_params}}\nexport JMX_PORT=9997\n# Add kafka sink to classpath and related depenencies\nif [ -e \"/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\" ]; then\n  export CLASSPATH=$CLASSPATH:/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\n  export CLASSPATH=$CLASSPATH:/usr/lib/ambari-metrics-kafka-sink/lib/*\nfi\n\nif [ -f /etc/kafka/conf/kafka-ranger-env.sh ]; then\n. /etc/kafka/conf/kafka-ranger-env.sh\nfi"
"is_supported_kafka_ranger : true"
"kafka_log_dir : /var/log/kafka"
"kafka_pid_dir : /var/run/kafka"
"kafka_user : kafka"
"kafka_user_nofile_limit : 128000"
"kafka_user_nproc_limit : 65536"

Caso seja realmente obrigatório obter cada chave e valor entre aspas duplas - use a seguinte modificação:

jq -r '.items[].properties | to_entries[]
       | "\"\(.key)\" : \"\(.value | gsub("\n";"\\n"))\","' input.json

A saída:

"content" : "\n#!/bin/bash\n\n# Set KAFKA specific environment variables here.\n\n# The java implementation to use.\nexport JAVA_HOME={{java64_home}}\nexport PATH=$PATH:$JAVA_HOME/bin\nexport PID_DIR={{kafka_pid_dir}}\nexport LOG_DIR={{kafka_log_dir}}\nexport KAFKA_KERBEROS_PARAMS={{kafka_kerberos_params}}\nexport JMX_PORT=9997\n# Add kafka sink to classpath and related depenencies\nif [ -e "/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar" ]; then\n  export CLASSPATH=$CLASSPATH:/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\n  export CLASSPATH=$CLASSPATH:/usr/lib/ambari-metrics-kafka-sink/lib/*\nfi\n\nif [ -f /etc/kafka/conf/kafka-ranger-env.sh ]; then\n. /etc/kafka/conf/kafka-ranger-env.sh\nfi",
"is_supported_kafka_ranger" : "true",
"kafka_log_dir" : "/var/log/kafka",
"kafka_pid_dir" : "/var/run/kafka",
"kafka_user" : "kafka",
"kafka_user_nofile_limit" : "128000",
"kafka_user_nproc_limit" : "65536",
RomanPerekhrest
fonte
Você defende o uso de uma ferramenta com reconhecimento de sintaxe ( jq) em vez de operações ingênuas de seqüência de caracteres, o que é bom, mas usa uma operação ingênua de seqüência de caracteres para executar (limitado) o processamento de seqüência de escape para saída. Isso não parece uma boa ideia para mim. jqdeve ter uma maneira de escapar adequadamente do valor de saída, certo?
Daniel Pryden
@DanielPryden, No. Embora jqtem algumas maneiras de escapar corretamente o valor para a saída (como @text, @shetc), aqueles não vai ajudar neste caso particular.
RomanPerekhrest 28/05
Uma variante que deixa os valores de propriedades como objetos JSON e usa sed para tirar as chaves indesejados e espaços em branco:jq '.items[].properties' input.json | sed -n 's/^\s\+//p'
Joe Lee-Moyet
por que "," não aparece na saída, como meus resultados esperados?
yael 30/05
você pode ver por favor minha "saída esperada", você pode editar sua resposta de acordo com meus resultados esperados?
yael 30/05
26

Por favor, não adquira o hábito de analisar dados estruturados com ferramentas não estruturadas. Se você estiver analisando XML, JSON, YAML etc., use um analisador específico, pelo menos para converter os dados estruturados em um formato mais apropriado para AWK sed, grepetc.

Nesse caso, gronajudaria bastante:

$ gron yourfile | grep -F .properties.
json.items[0].properties.content = "\n#!/bin/bash\n\n# Set KAFKA specific environment variables here.\n\n# The java implementation to use.\nexport JAVA_HOME={{java64_home}}\nexport PATH=/usr/lib/ccache:/home/steve/bin:/usr/local/bin:/usr/bin:/bin:/usr/bin/X11:/usr/games:/bin\nexport PID_DIR={{kafka_pid_dir}}\nexport LOG_DIR={{kafka_log_dir}}\nexport KAFKA_KERBEROS_PARAMS={{kafka_kerberos_params}}\nexport JMX_PORT=9997\n# Add kafka sink to classpath and related depenencies\nif [ -e \"/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\" ]; then\n  export CLASSPATH=:/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\n  export CLASSPATH=:/usr/lib/ambari-metrics-kafka-sink/lib/*\nfi\n\nif [ -f /etc/kafka/conf/kafka-ranger-env.sh ]; then\n. /etc/kafka/conf/kafka-ranger-env.sh\nfi";
json.items[0].properties.is_supported_kafka_ranger = "true";
json.items[0].properties.kafka_log_dir = "/var/log/kafka";
json.items[0].properties.kafka_pid_dir = "/var/run/kafka";
json.items[0].properties.kafka_user = "kafka";
json.items[0].properties.kafka_user_nofile_limit = "128000";
json.items[0].properties.kafka_user_nproc_limit = "65536";

(Você pode pós-processar isso | cut -d. -f4- | gron --ungronpara obter algo muito próximo da saída desejada, embora ainda como JSON válido.)

jqtambém é apropriado .

Stephen Kitt
fonte
2

From Sed - Introdução e Tutorial de Bruce Barnett :

sed -n '/properties/,/}$/ {
            /properties/n
            /}$/ !p
        }' FILE.json

Para uma correspondência mais exata e também para fechar as linhas de colchete com espaço em branco adicional, você pode usar

sed -E -n '/"properties" : {/,/^[[:blank:]]*}[[:blank:]]$/ {
               /"properties" : {/n
               /^[[:blank:]]*}[[:blank:]]$/ !p
           }' FILE.json
nohillside
fonte
Eu não estou familiarizado com JSON, mas talvez /}/seja mais seguro que /}$. O último parece não ter nenhuma vantagem de qualquer maneira.
Hauke ​​Laging
1
@HaukeLaging Sem o marcador de fim de linha, ele já corresponde à contentlinha que contém um }lugar.
Nohillside 27/05
5
Mesmo que seja possível, provavelmente funcionará apenas no arquivo de exemplo . Se você deseja analisar dados estruturados, deve usar algo projetado para isso. Seja jq, xpath, yq, xq, etc. Isso ocorre porque analisá-lo com ferramentas orientadas a linhas acabará mordendo você pelas costas e depurando que pode não ser muito fácil.
Nert 27/05
Por exemplo, o que acontece se um dos campos 'href' contiver a palavra "propriedades"?
Stig Hemmer
1
@StigHemmer É por isso que estendi o padrão no segundo exemplo. Mas concordo totalmente que usar gronou jqé a melhor abordagem.
Nohillside 28/05
2

sedum forro. Imprima linhas entre expressão regular properties(ou seja, linha contendo "propriedades") e expressão regular ^ *}(ou seja, linha começando com zero ou mais espaços seguidos de "}" e final de linha).

sed -n '/properties/,/^ *}$/{//!p}' file.json

awk um forro.

awk '/^ *}/{s=0}/properties/{getline;s=1}s' file.json
Steve
fonte
talvez você possa explicar como funciona a correspondência de padrões.
Vfbsilva 27/05
1
Embora isso funcione para o arquivo de exemplo fornecido, é arriscado tentar analisar o JSON com ferramentas que não o entendem. Por exemplo, o que acontece se um dos campos 'href' contiver a palavra "propriedades"? É muito menos propenso a erros de uma ferramenta compatível com JSON, como as respostas mais votadas.
Stig Hemmer
3
Concordo, arriscado. Mas a OP queria especificamente uma solução de uma linha usando sed / awk / perl. A resposta que eu dou atende a todos esses critérios.
steve
O que //!psignifica isso ? Imprimir, se não uma das coisas que combinam?
David Conrad
1
Ah, entendi, //repete o último regex, !não pimprima. Legal.
David Conrad
0

Ele está marcado perle não vejo perlresposta ainda, então vou entrar.

Não use expressões regulares ou outros analisadores 'não estruturados'. perltem o JSONmódulo com ele. ( JSON::PPfaz parte do núcleo desde 5.14 também)

#!/usr/bin/env perl

use strict;
use warnings;
use JSON;
use Data::Dumper;

my $str = do { local $/; <DATA> };

my $json = decode_json ( $str );

my $properties = $json -> {items} -> [0] -> {properties}; 

#dump the whole lot:
print Dumper $properties;


# or iterate
foreach my $key ( sort keys %$properties ) { 
   print "$key => ", $properties -> {$key},"\n";
}


__DATA__
{
  "href" : "http://master02:8080/api/v1/clusters/HDP/configurations?type=kafka-env&tag=version1527250007610",
  "items" : [
    {
      "href" : "http://master02:8080/api/v1/clusters/HDP/configurations?type=kafka-env&tag=version1527250007610",
      "tag" : "version1527250007610",
      "type" : "kafka-env",
      "version" : 8,
      "Config" : {
        "cluster_name" : "HDP",
        "stack_id" : "HDP-2.6"
      },
      "properties" : {
        "content" : "\n#!/bin/bash\n\n# Set KAFKA specific environment variables here.\n\n# The java implementation to use.\nexport JAVA_HOME={{java64_home}}\nexport PATH=$PATH:$JAVA_HOME/bin\nexport PID_DIR={{kafka_pid_dir}}\nexport LOG_DIR={{kafka_log_dir}}\nexport KAFKA_KERBEROS_PARAMS={{kafka_kerberos_params}}\nexport JMX_PORT=9997\n# Add kafka sink to classpath and related depenencies\nif [ -e \"/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\" ]; then\n  export CLASSPATH=$CLASSPATH:/usr/lib/ambari-metrics-kafka-sink/ambari-metrics-kafka-sink.jar\n  export CLASSPATH=$CLASSPATH:/usr/lib/ambari-metrics-kafka-sink/lib/*\nfi\n\nif [ -f /etc/kafka/conf/kafka-ranger-env.sh ]; then\n. /etc/kafka/conf/kafka-ranger-env.sh\nfi",
        "is_supported_kafka_ranger" : "true",
        "kafka_log_dir" : "/var/log/kafka",
        "kafka_pid_dir" : "/var/run/kafka",
        "kafka_user" : "kafka",
        "kafka_user_nofile_limit" : "128000",
        "kafka_user_nproc_limit" : "65536"
      }
    }
  ]
}

Naturalmente, você deve ler STDINou um nome de arquivo, e não DATAno seu cenário de uso real.

Sobrique
fonte