#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