Я хочу предоставить структурированный файл конфигурации, который максимально упрощен для нетехнического пользователя для редактирования (к сожалению, он должен быть файлом), и поэтому я хотел использовать YAML. Я не могу найти способ разбора этого из оболочки Unix script.
Как я могу проанализировать YAML файл из оболочки Linux script?
Ответ 1
Мой вариант использования может быть или не быть таким же, как то, что задавал этот оригинальный пост, но он определенно похож.
Мне нужно вытащить некоторый YAML в качестве переменных bash. YAML никогда не будет более одного уровня.
YAML выглядит так:
KEY: value
ANOTHER_KEY: another_value
OH_MY_SO_MANY_KEYS: yet_another_value
LAST_KEY: last_value
Выход вроде-a dis:
KEY="value"
ANOTHER_KEY="another_value"
OH_MY_SO_MANY_KEYS="yet_another_value"
LAST_KEY="last_value"
Я достиг результата с этой строкой:
sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
-
s/:[^:\/\/]/="/g
находит:
и заменяет его на="
, игнорируя://
(для URL-адресов) -
s/$/"/g
добавляет"
в конец каждой строки -
s/ *=/=/g
удаляет все пробелы перед=
Ответ 2
Вот bash -одно парсер, который использует sed и awk для анализа простых файлов yaml:
function parse_yaml {
local prefix=$2
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -ne "s|^\($s\):|\1|" \
-e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" $1 |
awk -F$fs '{
indent = length($1)/2;
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]}}
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) {vn=(vn)(vname[i])("_")}
printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, $2, $3);
}
}'
}
Он понимает такие файлы, как:
## global definitions
global:
debug: yes
verbose: no
debugging:
detailed: no
header: "debugging started"
## output
output:
file: "yes"
Который при анализе с использованием:
parse_yaml sample.yml
выведет:
global_debug="yes"
global_verbose="no"
global_debugging_detailed="no"
global_debugging_header="debugging started"
output_file="yes"
он также понимает файлы yaml, созданные ruby, которые могут включать в себя символы ruby, например:
---
:global:
:debug: 'yes'
:verbose: 'no'
:debugging:
:detailed: 'no'
:header: debugging started
:output: 'yes'
и будет выводиться так же, как в предыдущем примере.
Типичное использование в script:
eval $(parse_yaml sample.yml)
parse_yaml принимает аргумент префикса, так что все импортированные настройки имеют общий префикс (что уменьшит риск конфликтов пространства имен).
parse_yaml sample.yml "CONF_"
дает:
CONF_global_debug="yes"
CONF_global_verbose="no"
CONF_global_debugging_detailed="no"
CONF_global_debugging_header="debugging started"
CONF_output_file="yes"
Обратите внимание, что предыдущие настройки в файле можно ссылаться более поздними настройками:
## global definitions
global:
debug: yes
verbose: no
debugging:
detailed: no
header: "debugging started"
## output
output:
debug: $global_debug
Еще одно приятное использование - сначала проанализировать файл по умолчанию, а затем пользовательские настройки, который работает, поскольку последние настройки переопределяют первые:
eval $(parse_yaml defaults.yml)
eval $(parse_yaml project.yml)
Ответ 3
Я написал shyaml
в python для запросов YAML запросов из командной строки оболочки.
Обзор:
$ pip install shyaml ## installation
Пример файла YAML (со сложными функциями):
$ cat <<EOF > test.yaml
name: "MyName !!"
subvalue:
how-much: 1.1
things:
- first
- second
- third
other-things: [a, b, c]
maintainer: "Valentin Lab"
description: |
Multiline description:
Line 1
Line 2
EOF
Основной запрос:
$ cat test.yaml | shyaml get-value subvalue.maintainer
Valentin Lab
Более сложный цикл для сложных значений:
$ cat test.yaml | shyaml values-0 | \
while read -r -d $'\0' value; do
echo "RECEIVED: '$value'"
done
RECEIVED: '1.1'
RECEIVED: '- first
- second
- third'
RECEIVED: '2'
RECEIVED: 'Valentin Lab'
RECEIVED: 'Multiline description:
Line 1
Line 2'
Несколько ключевых моментов:
- все типы YAML и странности с синтаксисом корректно обрабатываются, как многострочные, цитируемые строки, встроенные последовательности...
-
\0
Доступен расширенный выход для сплошной многострочной манипуляции ввода. - простая пунктирная нотация для выбора под-значений (т.е.
subvalue.maintainer
является допустимым ключом). - доступ по индексу предоставляется последовательностям (т.е.
subvalue.things.-1
является последним элементом последовательностиsubvalue.things
.) - доступ ко всем элементам sequence/structs за один раз для использования в циклах bash.
- вы можете вывести всю подчасти файла YAML как... YAML, которые хорошо сочетаются для дальнейших манипуляций с shyaml.
Дополнительные примеры и документация доступны на странице shyaml github или shyaml PyPI.
Ответ 4
Можно передать небольшой скрипт некоторым интерпретаторам, таким как Python. Простым способом сделать это с помощью Ruby и его библиотеки YAML является следующее:
$ RUBY_SCRIPT="data = YAML::load(STDIN.read); puts data['a']; puts data['b']"
$ echo -e '---\na: 1234\nb: 4321' | ruby -ryaml -e "$RUBY_SCRIPT"
1234
4321
где data
являются хешем (или массивом) со значениями из yaml.
В качестве бонуса он будет разбирать Jekyll front matter просто отлично.
ruby -ryaml -e "puts YAML::load(open(ARGV.first).read)['tags']" example.md
Ответ 5
yq - это легкий и портативный YAML-процессор командной строки
Целью проекта является создание файлов jq или sed из yaml.
(http://mikefarah.github.io/yq/)
В качестве примера (украдено прямо из документации) приведен файл sample.yaml:
---
bob:
item1:
cats: bananas
item2:
cats: apples
затем
yq r sample.yaml bob.*.cats
будет выводить
- bananas
- apples
Ответ 6
Учитывая, что Python3 и PyYAML - это довольно простые зависимости, встречающиеся в настоящее время, следующие могут помочь:
yaml() {
python3 -c "import yaml;print(yaml.load(open('$1'))$2)"
}
VALUE=$(yaml ~/my_yaml_file.yaml "['a_key']")
Ответ 7
Сложно сказать, потому что это зависит от того, что вы хотите, чтобы анализатор извлекал из вашего документа YAML. Для простых случаев вы можете использовать grep
, cut
, awk
и т.д. Для более сложного разбора вам потребуется использовать полномасштабную библиотеку синтаксического анализа, такую как Python PyYAML или YAML:: Perl.
Ответ 8
Я просто написал парсер, который я назвал Yay! (Ямл не Ямскле!), Который анализирует Ямслеск, небольшое подмножество ЯМЛ. Итак, если вы ищете 100% -ный синтаксический анализатор YAML для Bash, то это не так. Однако, чтобы указать OP, если вы хотите, чтобы структурированный файл конфигурации был максимально простым для нетехнического пользователя для редактирования, похожим на YAML, это может представлять интерес.
Это воодушевленный более ранним ответом, но пишет ассоциативные массивы (да, для этого требуется Bash 4.x) вместо основных переменных, Он делает это таким образом, что позволяет анализировать данные без предварительного знания ключей, чтобы записывать управляемый данными код.
Как и элементы массива key/value, каждый массив имеет массив keys
, содержащий список имен ключей, массив children
, содержащий имена дочерних массивов, и ключ parent
, который ссылается на его родительский элемент.
Это является примером Ямслеска:
root_key1: this is value one
root_key2: "this is value two"
drink:
state: liquid
coffee:
best_served: hot
colour: brown
orange_juice:
best_served: cold
colour: orange
food:
state: solid
apple_pie:
best_served: warm
root_key_3: this is value three
Здесь показан пример, показывающий, как его использовать:
#!/bin/bash
# An example showing how to use Yay
. /usr/lib/yay
# helper to get array value at key
value() { eval echo \${$1[$2]}; }
# print a data collection
print_collection() {
for k in $(value $1 keys)
do
echo "$2$k = $(value $1 $k)"
done
for c in $(value $1 children)
do
echo -e "$2$c\n$2{"
print_collection $c " $2"
echo "$2}"
done
}
yay example
print_collection example
который выводит:
root_key1 = this is value one
root_key2 = this is value two
root_key_3 = this is value three
example_drink
{
state = liquid
example_coffee
{
best_served = hot
colour = brown
}
example_orange_juice
{
best_served = cold
colour = orange
}
}
example_food
{
state = solid
example_apple_pie
{
best_served = warm
}
}
И здесь является синтаксический анализатор:
yay_parse() {
# find input file
for f in "$1" "$1.yay" "$1.yml"
do
[[ -f "$f" ]] && input="$f" && break
done
[[ -z "$input" ]] && exit 1
# use given dataset prefix or imply from file name
[[ -n "$2" ]] && local prefix="$2" || {
local prefix=$(basename "$input"); prefix=${prefix%.*}
}
echo "declare -g -A $prefix;"
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -n -e "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$input" |
awk -F$fs '{
indent = length($1)/2;
key = $2;
value = $3;
# No prefix or parent for the top level (indent zero)
root_prefix = "'$prefix'_";
if (indent ==0 ) {
prefix = ""; parent_key = "'$prefix'";
} else {
prefix = root_prefix; parent_key = keys[indent-1];
}
keys[indent] = key;
# remove keys left behind if prior row was indented more than this row
for (i in keys) {if (i > indent) {delete keys[i]}}
if (length(value) > 0) {
# value
printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
} else {
# collection
printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);
printf("%s%s[parent]=\"%s%s\";\n", root_prefix, key, prefix, parent_key);
}
}'
}
# helper to load yay data file
yay() { eval $(yay_parse "[email protected]"); }
В связанном исходном файле есть документация, а ниже приведено краткое описание того, что делает код.
Функция yay_parse
сначала находит файл input
или завершает работу с состоянием выхода из 1. Затем он определяет набор данных prefix
, явно заданный или полученный из имени файла.
Он записывает правильные команды bash
на свой стандартный вывод, который, если выполняется, определяет массивы, представляющие содержимое файла входных данных. Первый из них определяет массив верхнего уровня:
echo "declare -g -A $prefix;"
Обратите внимание, что объявления массива ассоциативны (-A
), что является признаком Bash версии 4. Объявления также глобальны (-g
), поэтому они могут быть выполнены в функции, но доступны для глобальной области, например помощник yay
:
yay() { eval $(yay_parse "[email protected]"); }
Входные данные изначально обрабатываются с помощью sed
. Он отбрасывает строки, которые не соответствуют спецификации формата Ямслеска, прежде чем ограничивать действительные поля Ямслеска символом ASCII File Separator и удалять любые двойные кавычки окружая поле значений.
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -n -e "s|^\($s\)\($w\)$s:$s\"\(.*\)\"$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" "$input" |
Два выражения похожи; они отличаются только тем, что первый выбирает цитированные значения, где второй выбирает некотируемые.
File Separator (28/hex 12/восьмеричный 034) используется, потому что, как непечатаемый символ, вряд ли во входных данных.
Результат передается по каналу в awk
, который обрабатывает его вход по одной строке за раз. Он использует символ FS для назначения каждому полю переменной:
indent = length($1)/2;
key = $2;
value = $3;
Все строки имеют отступ (возможно, ноль) и ключ, но все они не имеют значения. Он вычисляет уровень отступа для строки, делящей длину первого поля, содержащего ведущие пробелы, на два. Элементы верхнего уровня без какого-либо отступ находятся на нулевом уровне.
Далее, он определяет, что prefix
использовать для текущего элемента. Это то, что добавляется к имени ключа, чтобы создать имя массива. Там root_prefix
для массива верхнего уровня, который определяется как имя набора данных и подчеркивание:
root_prefix = "'$prefix'_";
if (indent ==0 ) {
prefix = ""; parent_key = "'$prefix'";
} else {
prefix = root_prefix; parent_key = keys[indent-1];
}
parent_key
- это ключ на уровне отступа выше текущего уровня отступа строки и представляет коллекцию, частью которой является текущая строка. Группы ключей/значений коллекции будут храниться в массиве с его именем, определенным как конкатенация prefix
и parent_key
.
Для верхнего уровня (нулевой уровень отступа) префикс набора данных используется в качестве родительского ключа, поэтому он не имеет префикса (он установлен в ""
). Все остальные массивы имеют префикс корня.
Затем текущий ключ вставляется в массив (awk-internal), содержащий ключи. Этот массив сохраняется на протяжении всей сессии awk и поэтому содержит ключи, вставленные предыдущими строками. Ключ вставляется в массив, используя его отступ в качестве индекса массива.
keys[indent] = key;
Поскольку этот массив содержит ключи от предыдущих строк, все клавиши с уровнем углубления уровня, отличным от текущего уровня отступа строки, удаляются:
for (i in keys) {if (i > indent) {delete keys[i]}}
Это оставляет массив ключей, содержащий цепочку ключей от корня на уровне отступа 0 до текущей строки. Он удаляет устаревшие ключи, которые остаются, когда предыдущая строка была отступом глубже, чем текущая строка.
В последнем разделе выводятся команды bash
: строка ввода без значения начинает новый уровень отступа (коллекция в языке YAML), а строка ввода со значением добавляет ключ к текущей коллекции.
Имя коллекции представляет собой конкатенацию текущей строки prefix
и parent_key
.
Когда ключ имеет значение, клавиша с этим значением назначается текущей коллекции следующим образом:
printf("%s%s[%s]=\"%s\";\n", prefix, parent_key , key, value);
printf("%s%s[keys]+=\" %s\";\n", prefix, parent_key , key);
Первый оператор выводит команду для присвоения значения ассоциативному элементу массива, названному после ключа, а второй выводит команду на добавление ключа в список keys
, ограниченный коллекцией:
<current_collection>[<key>]="<value>";
<current_collection>[keys]+=" <key>";
Если ключ не имеет значения, запускается новая коллекция следующим образом:
printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);
Первый оператор выводит команду для добавления новой коллекции в текущий список с ограниченным пространством children
, а второй выводит команду для объявления нового ассоциативного массива для новой коллекции:
<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;
Весь вывод из yay_parse
может быть проанализирован как команды Bash с помощью встроенных команд Bash eval
или source
.
Ответ 9
вот расширенная версия ответа Стефана Фарештама:
function parse_yaml {
local prefix=$2
local s='[[:space:]]*' w='[a-zA-Z0-9_]*' fs=$(echo @|tr @ '\034')
sed -ne "s|,$s\]$s\$|]|" \
-e ":1;s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s,$s\(.*\)$s\]|\1\2: [\3]\n\1 - \4|;t1" \
-e "s|^\($s\)\($w\)$s:$s\[$s\(.*\)$s\]|\1\2:\n\1 - \3|;p" $1 | \
sed -ne "s|,$s}$s\$|}|" \
-e ":1;s|^\($s\)-$s{$s\(.*\)$s,$s\($w\)$s:$s\(.*\)$s}|\1- {\2}\n\1 \3: \4|;t1" \
-e "s|^\($s\)-$s{$s\(.*\)$s}|\1-\n\1 \2|;p" | \
sed -ne "s|^\($s\):|\1|" \
-e "s|^\($s\)-$s[\"']\(.*\)[\"']$s\$|\1$fs$fs\2|p" \
-e "s|^\($s\)-$s\(.*\)$s\$|\1$fs$fs\2|p" \
-e "s|^\($s\)\($w\)$s:$s[\"']\(.*\)[\"']$s\$|\1$fs\2$fs\3|p" \
-e "s|^\($s\)\($w\)$s:$s\(.*\)$s\$|\1$fs\2$fs\3|p" | \
awk -F$fs '{
indent = length($1)/2;
vname[indent] = $2;
for (i in vname) {if (i > indent) {delete vname[i]; idx[i]=0}}
if(length($2)== 0){ vname[indent]= ++idx[indent] };
if (length($3) > 0) {
vn=""; for (i=0; i<indent; i++) { vn=(vn)(vname[i])("_")}
printf("%s%s%s=\"%s\"\n", "'$prefix'",vn, vname[indent], $3);
}
}'
}
Эта версия поддерживает обозначение -
и сокращенное обозначение словарей и списков. Следующий вход:
global:
input:
- "main.c"
- "main.h"
flags: [ "-O3", "-fpic" ]
sample_input:
- { property1: value, property2: "value2" }
- { property1: "value3", property2: 'value 4' }
производит этот вывод:
global_input_1="main.c"
global_input_2="main.h"
global_flags_1="-O3"
global_flags_2="-fpic"
global_sample_input_1_property1="value"
global_sample_input_1_property2="value2"
global_sample_input_2_property1="value3"
global_sample_input_2_property2="value 4"
как вы можете видеть -
элементы автоматически нумеруются, чтобы получить разные имена переменных для каждого элемента. В bash
нет многомерных массивов, так что это один из способов обхода. Несколько уровней поддерживаются. Чтобы обойти проблему с конечными пробелами, упомянутую @briceburg, нужно заключить значения в одинарные или двойные кавычки. Тем не менее, есть некоторые ограничения: Расширение словарей и списков может давать неправильные результаты, если значения содержат запятые. Кроме того, более сложные структуры, такие как значения, охватывающие несколько строк (например, ssh-ключи), (пока) не поддерживаются.
Несколько слов о коде: Первая команда sed
расширяет краткую форму словарей { key: value,...}
до обычных и преобразует их в более простой стиль yaml. Второй вызов sed
делает то же самое для кратких обозначений списков и преобразует [ entry,... ]
в подробный список с обозначением -
. Третий вызов sed
является исходным, который обрабатывает обычные словари, теперь с добавлением обработки списков с -
и отступами. Часть awk
вводит индекс для каждого уровня отступа и увеличивает его, когда имя переменной пустое (т.е. при обработке списка). Текущее значение счетчиков используется вместо пустого vname. При подъеме на один уровень счетчики обнуляются.
Изменение: я создал хранилище GitHub для этого.
Ответ 10
Другой вариант - преобразовать YAML в JSON, а затем использовать jq для взаимодействия с представлением JSON либо для извлечения из него информации, либо для ее редактирования.
Я написал простой bash script, который содержит этот клей - см. проект Y2J на GitHub
Ответ 11
perl -ne 'chomp; printf qq/%s="%s"\n/, split(/\s*:\s*/,$_,2)' file.yml > file.sh
Ответ 12
Я нашел этот Jshon tool лучшим для этой цели, но в мире JSON.
Но я не нашел никаких следов в Интернете такого инструмента для YAML. Вы должны (по крайней мере, на данный момент) использовать Perl/Python/Ruby script для этого (как в предыдущих ответах).
Ответ 13
Если вам нужно одно значение, вы можете использовать инструмент, который преобразует ваш YAML-документ в JSON и jq
в jq
, например, yq
.
Содержание sample.yaml:
---
bob:
item1:
cats: bananas
item2:
cats: apples
thing:
cats: oranges
Пример:
$ yq -r '.bob["thing"]["cats"]' sample.yaml
oranges
Ответ 14
Я знаю, что это очень специфично, но я думаю, что мой ответ может быть полезен для определенных пользователей.
Если на вашем компьютере установлены node
и npm
, вы можете использовать js-yaml
.
Сначала установите:
npm i -g js-yaml
# or locally
npm i js-yaml
то в вашем сценарии bash
#!/bin/bash
js-yaml your-yaml-file.yml
Также, если вы используете jq
вы можете сделать что-то подобное
#!/bin/bash
json="$(js-yaml your-yaml-file.yml)"
aproperty="$(jq '.apropery' <<< "$json")"
echo "$aproperty"
Поскольку js-yaml
преобразует файл yaml в литерал json string. Затем вы можете использовать строку с любым парсером json в вашей системе unix.
Ответ 15
Если у вас есть python 2 и PyYAML, вы можете использовать этот парсер, который я написал, называемый parse_yaml.py. Некоторые из более аккуратных вещей, которые он делает, позволяют вам выбрать префикс (в случае, если у вас есть более одного файла с похожими переменными) и выбрать одно значение из файла yaml.
Например, если у вас есть эти файлы yaml:
staging.yaml:
db:
type: sqllite
host: 127.0.0.1
user: dev
password: password123
prod.yaml:
db:
type: postgres
host: 10.0.50.100
user: postgres
password: password123
Вы можете загрузить оба без конфликтов.
$ eval $(python parse_yaml.py prod.yaml --prefix prod --cap)
$ eval $(python parse_yaml.py staging.yaml --prefix stg --cap)
$ echo $PROD_DB_HOST
10.0.50.100
$ echo $STG_DB_HOST
127.0.0.1
И даже вишня выбирает нужные вам ценности.
$ prod_user=$(python parse_yaml.py prod.yaml --get db_user)
$ prod_port=$(python parse_yaml.py prod.yaml --get db_port --default 5432)
$ echo prod_user
postgres
$ echo prod_port
5432
Ответ 16
Вы можете использовать эквивалент yq, который написан в golang:
./go-yg -yamlFile /home/user/dev/ansible-firefox/defaults/main.yml -key
firefox_version
возвращает:
62.0.3
Ответ 17
Вы также можете использовать Grunt (Running JavaScript Task Runner). Может быть легко интегрирована с оболочкой. Он поддерживает чтение файлов YAML (grunt.file.readYAML
) и JSON (grunt.file.readJSON
).
Это может быть достигнуто путем создания задачи в Gruntfile.js
(или Gruntfile.coffee
), например:
module.exports = function (grunt) {
grunt.registerTask('foo', ['load_yml']);
grunt.registerTask('load_yml', function () {
var data = grunt.file.readYAML('foo.yml');
Object.keys(data).forEach(function (g) {
// ... switch (g) { case 'my_key':
});
});
};
то из оболочки просто запустите grunt foo
(отметьте grunt --help
для доступных задач).
Кроме того, вы можете реализовать задачи exec:foo
(grunt-exec
) с входными переменными, переданными из вашей задачи (foo: { cmd: 'echo bar <%= foo %>' }
), чтобы распечатать вывод в любом формате, а затем передать его в другую команду.
Существует также аналогичный инструмент для Grunt, он называется gulp с дополнительным плагином gulp-yaml.
Установить через: npm install --save-dev gulp-yaml
Использование образца:
var yaml = require('gulp-yaml');
gulp.src('./src/*.yml')
.pipe(yaml())
.pipe(gulp.dest('./dist/'))
gulp.src('./src/*.yml')
.pipe(yaml({ space: 2 }))
.pipe(gulp.dest('./dist/'))
gulp.src('./src/*.yml')
.pipe(yaml({ safe: true }))
.pipe(gulp.dest('./dist/'))
Чтобы узнать больше о формате YAML, отметьте сайт YAML для доступных проектов, библиотек и других ресурсов, которые могут помочь вам разобрать этот формат.
Другие инструменты:
-
анализирует, считывает и создает JSON
Ответ 18
yaml.sh - это синтаксический анализатор bash yaml, похожий на ответы awk и sed, но завернутый с приятным api, который вы можете использовать непосредственно в своем скрипте.