Преобразовать файл ключ: значение с комментариями в документ JSON с помощью инструментов UNIX - PullRequest
1 голос
/ 08 марта 2019

У меня есть файл в подмножестве YAML с данными, подобными приведенным ниже:

# This is a comment
# This is another comment


spark:spark.ui.enabled: 'false'
spark:spark.sql.adaptive.enabled: 'true'
yarn:yarn.nodemanager.log.retain-seconds: '259200'


Мне нужно преобразовать это в документ JSON, похожий на этот (обратите внимание, что строки, содержащие логические и целые числа, все еще остаютсяstrings):

{
  "spark:spark.ui.enabled": "false",
  "spark:spark.sql.adaptive.enabled": "true",
  "yarn:yarn.nodemanager.log.retain-seconds", "259200"
}

Самое близкое, что я получил, было это:

cat << EOF > ./file.yaml
> # This is a comment
> # This is another comment
> 
> 
> spark:spark.ui.enabled: 'false'
> spark:spark.sql.adaptive.enabled: 'true'
> yarn:yarn.nodemanager.log.retain-seconds: '259200'
> EOF
echo {$(cat file.yaml | grep -o '^[^#]*' | sed '/^$/d' | awk -F": " '{sub($1, "\"&\""); print}' | paste -sd "," -  )}

, который, кроме довольно грубого взгляда, не дает правильного ответа, он возвращает:

{"spark:spark.ui.enabled": 'false',"spark:spark.sql.adaptive.enabled": 'true',"dataproc:dataproc.monitoring.stackdriver.enable": 'true',"spark:spark.submit.deployMode": 'cluster'}

, который, если я передаю трубку на jq, вызывает ошибку разбора.

Я надеюсь, что мне не хватает гораздо более простого способа сделать это, но я не могу понять это.Кто-нибудь может помочь?

Ответы [ 3 ]

3 голосов
/ 08 марта 2019

Реализовано в чистом jq (протестировано с версией 1.6):

#!/usr/bin/env bash

jq_script=$(cat <<'EOF'
def content_for_line:
  "^[[:space:]]*([#]|$)" as $ignore_re |           # regex for comments, blank lines
  "^(?<key>.*): (?<value>.*)$" as $content_re |    # regex for actual k/v pairs
  "^'(?<value>.*)'$" as $quoted_re |               # regex for values in single quotes
  if test($ignore_re) then {} else                 # empty lines add nothing to the data
    if test($content_re) then (                    # non-empty: match against $content_re
      capture($content_re) as $content |           # ...and put the groups into $content
      $content.key as $key |                       # string before ": " becomes $key
      (if ($content.value | test($quoted_re)) then # if value contains literal quotes...
         ($content.value | capture($quoted_re)).value # ...take string from inside quotes
       else
         $content.value                               # no quotes to strip
       end) as $value |                     # result of the above block becomes $value
      {"\($key)": "\($value)"}              # and return a map from one key to one value
    ) else
      # we get here if a line didn't match $ignore_re *or* $content_re
      error("Line \(.) is not recognized as a comment, empty, or valid content")
    end
  end;

# iterate over our input lines, passing each one to content_for_line and merging the result
# into the object we're building, which we eventually return as our result.
reduce inputs as $item ({}; . + ($item | content_for_line))
EOF
)

# jq -R: read input as raw strings
# jq -n: don't read from stdin until requested with "input" or "inputs"
jq -Rn "$jq_script" <file.yaml >file.json

В отличие от инструментов, не поддерживающих синтаксис, это может никогда генерировать вывод, который не является допустимым JSON;и его можно легко расширить с помощью прикладной логики (f / e, чтобы выдавать некоторые значения, но не другие в виде числовых литералов, а не строковых литералов), добавив дополнительный этап фильтрации для проверки и изменения вывода content_for_line.

1 голос
/ 09 марта 2019

Вот простое, но простое решение:

def tidy: sub("^ *'?";"") | sub(" *'?$";"");
def kv: split(":") | [ (.[:-1] | join(":")), (.[-1]|tidy)];

reduce (inputs| select( test("^ *#|^ *$")|not) | kv) as $row ({};
    .[$row[0]] = $row[1] )

Воззвание

jq -n -R -f tojson.jq input.txt
0 голосов
/ 08 марта 2019

Вы можете сделать все это в awk, используя gsub и sprintf, например:

(изменить, чтобы добавить "," разделение записей JSON)

awk 'BEGIN {ol=0; print "{" } 
/^[^#]/ { 
    if (ol) print ","
    gsub ("\047", "\042")
    $1 = sprintf ("  \"%s\":", substr ($1, 1, length ($1) - 1))
    printf "%s %s", $1, $2
    ol++ 
} 
END { print "\n}" }' file.yaml

( примечание: , хотя jq является подходящим инструментом для форматирования json)

Объяснение

  • awk 'BEGIN { ol=0; print "{" } вызов awk настройка строки вывода переменная ol=0 для "," управления выводом и печать заголовка "{",
  • /^[^#]/ { соответствует только строкам без комментариев,
  • if (ol) print ",", если строка вывода ol больше нуля, вывести трейлинг ","
  • gsub ("\047", "\042") заменить все одинарные кавычки на двойные,
  • $1 = sprintf (" \"%s\":", substr ($1, 1, length ($1) - 1)) добавить 2 первых пробела и двойные кавычки вокруг первого поля (кроме последнего символа), а затем добавить ':' в конце.
  • print $1, $2 вывод переформатированных полей,
  • ol++ увеличить счетчик выходной строки и
  • END { print "}" }' закрыть, напечатав нижний колонтитул "}"

Пример использования / Вывод

Просто выберите / вставьте команду awk выше (при необходимости изменив имя файла)

$ awk 'BEGIN {ol=0; print "{" }
> /^[^#]/ {
>     if (ol) print ","
>     gsub ("\047", "\042")
>     $1 = sprintf ("  \"%s\":", substr ($1, 1, length ($1) - 1))
>     printf "%s %s", $1, $2
>     ol++
> }
> END { print "\n}" }' file.yaml
{
  "spark:spark.ui.enabled": "false",
  "spark:spark.sql.adaptive.enabled": "true"
}
...