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

#bash #shell #awk #jq

#bash #оболочка #awk #jq

Вопрос:

У меня есть файл в подмножестве 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, выглядящий следующим образом (обратите внимание, что строки, содержащие логические значения и целые числа, по-прежнему остаются строками):

 {
  "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, ""amp;""); 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 , вызывает ошибку синтаксического анализа.

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

Комментарии:

1. простите мою наивность, я думаю об этих инструментах как о части bash, и рад, что их исправили. Я ограничен инструментами, доступными мне в образе docker, в котором я запускаю это, этот образ создан на базе debian.

2. Создайте новый образ docker, который включает инструменты, необходимые для работы с YAML и JSON.

3. Я просто хочу разобраться с этим конкретным вводом, возможно, упоминание yaml было ошибкой. У меня есть jq в наличии

4. там тоже есть целые числа. Я отредактировал приведенный выше пример, чтобы отразить это

5. Если вы привыкли к jq , то для этого также есть оболочка под названием yq , которая обрабатывает YAML: yq.readthedocs.io/en/latest

Ответ №1:

Реализовано в чистом виде 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; и это может быть легко расширено с помощью логики конкретного приложения (например, для выдачи некоторых значений, но не других, в виде числовых литералов, а не строковых литералов), добавив дополнительный этап фильтрации для проверки и изменения выходных данных content_for_line .

Комментарии:

1. о боже, я не знаю, как это сработало, но это сработало. Большое тебе спасибо, Чарльз. Теперь я проведу остаток дня, разбирая его и пытаясь понять 🙂 еще раз спасибо

2. (Кроме того, я только что протестировал его в своем реальном документе yaml, который намного больше, чем образец, который я разместил здесь, и он отлично сработал)

3. Просто добавил несколько комментариев; надеюсь, они облегчат отслеживание. Дайте мне знать, если требуются пояснения в других местах.

Ответ №2:

Вот простое, но без излишеств решение:

 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
  

Ответ №3:

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

(отредактируйте, чтобы добавить "," разделяющие записи json)

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

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

Объяснение

  • awk 'BEGIN { ol=0; print "{" } вызовите awk настройку переменной строки вывода для ol=0 управления выводом и распечатайте заголовок "," …………. "{" ,
  • /^[^#]/ { сопоставляйте только строки без комментариев,
  • if (ol) print "," если выходная строка ol больше нуля, выведите конечную ","
  • gsub ("47", "42") замените все одинарные кавычки двойными,
  • $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 ("47", "42")
>     $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"
}
  

Комментарии:

1. спасибо, Дэвид, это близко, но в нем не ставится запятая после первого элемента, поэтому недопустимый JSON