我希望提供一个结构化的配置文件,这对非技术用户来说很容易编辑(不幸的是它必须是一个文件),所以我想使用YAML.但是我找不到从Unix shell脚本解析这个问题的方法.
Ste*_*tam 256
这是一个仅限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);
}
}'
}
Run Code Online (Sandbox Code Playgroud)
它理解如下文件:
## global definitions
global:
debug: yes
verbose: no
debugging:
detailed: no
header: "debugging started"
## output
output:
file: "yes"
Run Code Online (Sandbox Code Playgroud)
哪个,使用时解析:
parse_yaml sample.yml
Run Code Online (Sandbox Code Playgroud)
将输出:
global_debug="yes"
global_verbose="no"
global_debugging_detailed="no"
global_debugging_header="debugging started"
output_file="yes"
Run Code Online (Sandbox Code Playgroud)
它还理解由ruby生成的yaml文件,其中可能包含ruby符号,如:
---
:global:
:debug: 'yes'
:verbose: 'no'
:debugging:
:detailed: 'no'
:header: debugging started
:output: 'yes'
Run Code Online (Sandbox Code Playgroud)
并将输出与上一个示例相同的内容.
脚本中的典型用法是:
eval $(parse_yaml sample.yml)
Run Code Online (Sandbox Code Playgroud)
parse_yaml接受前缀参数,以便导入的设置都具有公共前缀(这将降低命名空间冲突的风险).
parse_yaml sample.yml "CONF_"
Run Code Online (Sandbox Code Playgroud)
收益率:
CONF_global_debug="yes"
CONF_global_verbose="no"
CONF_global_debugging_detailed="no"
CONF_global_debugging_header="debugging started"
CONF_output_file="yes"
Run Code Online (Sandbox Code Playgroud)
请注意,以后的设置可以引用文件中的先前设置:
## global definitions
global:
debug: yes
verbose: no
debugging:
detailed: no
header: "debugging started"
## output
output:
debug: $global_debug
Run Code Online (Sandbox Code Playgroud)
另一个不错的用法是首先解析默认文件然后解析用户设置,因为后面的设置会覆盖第一个设置:
eval $(parse_yaml defaults.yml)
eval $(parse_yaml project.yml)
Run Code Online (Sandbox Code Playgroud)
vaa*_*aab 91
我已经shyaml
在shell命令行中用python 编写了YAML查询需求.
概述:
$ pip install shyaml ## installation
Run Code Online (Sandbox Code Playgroud)
示例的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
Run Code Online (Sandbox Code Playgroud)
基本查询:
$ cat test.yaml | shyaml get-value subvalue.maintainer
Valentin Lab
Run Code Online (Sandbox Code Playgroud)
复杂值的更复杂的循环查询:
$ 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'
Run Code Online (Sandbox Code Playgroud)
几个关键点:
\0
填充输出可用于实体多行输入操作. subvalue.maintainer
是一个有效的键).subvalue.things.-1
是序列的最后一个元素subvalue.things
.)shyaml github页面或shyaml PyPI页面上提供了更多示例和文档.
Cur*_*ell 51
我的用例可能与原始帖子提出的内容完全相同或不同,但它绝对相似.
我需要将一些YAML作为bash变量引入.YAML永远不会超过一个级别.
YAML看起来像这样:
KEY: value
ANOTHER_KEY: another_value
OH_MY_SO_MANY_KEYS: yet_another_value
LAST_KEY: last_value
Run Code Online (Sandbox Code Playgroud)
输出就像一个dis:
KEY="value"
ANOTHER_KEY="another_value"
OH_MY_SO_MANY_KEYS="yet_another_value"
LAST_KEY="last_value"
Run Code Online (Sandbox Code Playgroud)
我用这条线实现了输出:
sed -e 's/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g' file.yaml > file.sh
Run Code Online (Sandbox Code Playgroud)
s/:[^:\/\/]/="/g
查找:
并替换它="
,同时忽略://
(对于URL)s/$/"/g
追加"
到每一行的末尾s/ *=/=/g
之前删除所有空格 =
Raf*_*ael 31
可以将一个小脚本传递给一些解释器,比如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
Run Code Online (Sandbox Code Playgroud)
,其中data
是一个带有yaml值的哈希(或数组).
作为奖励,它会解析Jekyll的前端问题.
ruby -ryaml -e "puts YAML::load(open(ARGV.first).read)['tags']" example.md
Run Code Online (Sandbox Code Playgroud)
bma*_*pin 26
yq是轻巧且可移植的命令行YAML处理器
该项目的目的是将yaml文件作为jq或sed。
(http://mikefarah.github.io/yq/)
作为一个示例(直接从文档中被盗),给出了一个sample.yaml文件:
---
bob:
item1:
cats: bananas
item2:
cats: apples
Run Code Online (Sandbox Code Playgroud)
然后
yq r sample.yaml bob.*.cats
Run Code Online (Sandbox Code Playgroud)
将输出
- bananas
- apples
Run Code Online (Sandbox Code Playgroud)
Tor*_*ger 18
鉴于Python3和PyYAML是现在相当容易满足的依赖项,以下内容可能会有所帮助:
yaml() {
python3 -c "import yaml;print(yaml.load(open('$1'))$2)"
}
VALUE=$(yaml ~/my_yaml_file.yaml "['a_key']")
Run Code Online (Sandbox Code Playgroud)
dog*_*ane 11
很难说,因为它取决于您希望解析器从YAML文档中提取的内容.对于简单的情况下,可能能够使用grep
,cut
,awk
等对于更复杂的分析,你需要使用一个全面的解析库,如Python的PyYAML或YAML :: Perl的.
Ini*_*ian 11
将我的答案从How to convert a json response into yaml in bash,因为这似乎是处理从命令行解析 YAML 文本的权威帖子。
我想添加有关yq
YAML 实现的详细信息。由于此 YAML 解析器有两个实现,都具有名称yq
,因此如果不查看实现的 DSL,就很难区分正在使用的是哪个。有两种可用的实现是
jq
,使用 Python 编写,使用 PyYAML 库进行 YAML 解析两者都可以通过几乎所有主要发行版上的标准安装包管理器进行安装
两个版本都比另一个版本有一些优点和缺点,但要强调一些有效的要点(从他们的回购说明中采用)
基斯柳克/yq
jq
,对于熟悉后者的用户来说,解析和操作变得非常简单jq
不保留评论,在往返转换期间,评论会丢失。xq
它使用xmltodict将 XML 转码为 JSON并将其通过管道传输到jq
,您可以在其上应用相同的 DSL 对对象执行 CRUD 操作并将输出返回到 XML。-i
标志的就地编辑模式(类似于sed -i
)迈克法拉/yq
-i
标志的就地编辑模式(类似于sed -i
)-C
标志(不适用于 JSON 输出)和子元素的缩进(默认为 2 个空格)为输出 YAML 着色我对以下两个版本的 YAML(也在其他答案中引用)的看法
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
Run Code Online (Sandbox Code Playgroud)
两种实现都要执行的各种操作(一些经常使用的操作)
root_key2
coffee
orange_juice
food
使用 kislyuk/yq
yq -y '.root_key2 |= "this is a new value"' yaml
Run Code Online (Sandbox Code Playgroud)
yq -y '.drink.coffee += { time: "always"}' yaml
Run Code Online (Sandbox Code Playgroud)
yq -y 'del(.drink.orange_juice.colour)' yaml
Run Code Online (Sandbox Code Playgroud)
yq -r '.food|paths(scalars) as $p | [($p|join(".")), (getpath($p)|tojson)] | @tsv' yaml
Run Code Online (Sandbox Code Playgroud)
这很简单。您所需要的只是将jq
JSON 输出转码回带有该-y
标志的YAML 。
使用 mikefarah/yq
yq w yaml root_key2 "this is a new value"
Run Code Online (Sandbox Code Playgroud)
yq w yaml drink.coffee.time "always"
Run Code Online (Sandbox Code Playgroud)
yq d yaml drink.orange_juice.colour
Run Code Online (Sandbox Code Playgroud)
yq r yaml --printMode pv "food.**"
Run Code Online (Sandbox Code Playgroud)
截至 2020 年 12 月 21 日,yq
v4 处于测试阶段,支持强大的路径表达式并支持类似于使用jq
. 阅读过渡说明 -从 V3 升级
sta*_*fry 10
我刚写了一个叫做Yay的解析器!(Yaml不是Yamlesque!),它解析YAMLES的一小部分Yamlesque.所以,如果你正在为Bash寻找100%兼容的YAML解析器,那么这不是它.但是,引用OP,如果您想要一个非技术用户编辑的结构化配置文件,这类文件与 YAML类似,那么这可能是有意义的.
它受到早期答案的启发,但编写关联数组(是的,它需要Bash 4.x)而不是基本变量.它以允许在不事先知道密钥的情况下解析数据的方式这样做,从而可以编写数据驱动的代码.
除了键/值数组元素外,每个数组都有一个keys
包含键名列表的children
数组,一个包含子数组名称的数组和一个parent
引用其父数组的键.
这是Yamlesque的一个例子:
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
Run Code Online (Sandbox Code Playgroud)
这是一个显示如何使用它的示例:
#!/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
Run Code Online (Sandbox Code Playgroud)
哪个输出:
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
}
}
Run Code Online (Sandbox Code Playgroud)
而这里是解析器:
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 "$@"); }
Run Code Online (Sandbox Code Playgroud)
链接源文件中有一些文档,下面是代码的简短说明.
该yay_parse
函数首先定位input
文件或以退出状态1退出.接下来,它确定数据集prefix
,显式指定或从文件名派生.
它将有效bash
命令写入其标准输出,如果执行该命令,则定义表示输入数据文件内容的数组.第一个定义了顶级数组:
echo "declare -g -A $prefix;"
Run Code Online (Sandbox Code Playgroud)
请注意,数组声明是associative(-A
),它是Bash版本4的一个特性.声明也是global(-g
),因此它们可以在函数中执行,但可以像yay
helper 一样可用于全局作用域:
yay() { eval $(yay_parse "$@"); }
Run Code Online (Sandbox Code Playgroud)
输入数据最初用sed
.处理.在使用ASCII 文件分隔符字符分隔有效的Yamlesque字段并删除值字段周围的任何双引号之前,它会删除与Yamlesque格式规范不匹配的行.
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" |
Run Code Online (Sandbox Code Playgroud)
这两个表达方式相似; 它们之所以不同只是因为第一个选择了引用的值,而第二个选择了未引用的值.
使用文件分隔符(28 /十六进制12 /八进制034),因为作为不可打印的字符,它不太可能在输入数据中.
结果通过管道awk
输入其一次输入一行的过程.它使用FS字符将每个字段分配给变量:
indent = length($1)/2;
key = $2;
value = $3;
Run Code Online (Sandbox Code Playgroud)
所有行都有缩进(可能为零)和一个键,但它们都没有值.它计算将包含前导空格的第一个字段的长度除以2的行的缩进级别.没有任何缩进的顶级项目的缩进级别为零.
接下来,它prefix
确定了当前项目的用途.这是添加到键名称以生成数组名称的内容.有一个root_prefix
用于其被定义为所述数据集名称和下划线顶层数组:
root_prefix = "'$prefix'_";
if (indent ==0 ) {
prefix = ""; parent_key = "'$prefix'";
} else {
prefix = root_prefix; parent_key = keys[indent-1];
}
Run Code Online (Sandbox Code Playgroud)
它parent_key
是当前行的缩进级别上方的缩进级别的键,表示当前行所属的集合.集合的键/值对将被存储在与它的名称的数组定义为串联prefix
和parent_key
.
对于顶级(缩进级别为零),数据集前缀用作父键,因此它没有前缀(它设置为""
).所有其他数组都以前缀为前缀.
接下来,将当前密钥插入包含密钥的(awk-internal)数组中.该数组在整个awk会话中持续存在,因此包含由前一行插入的键.使用其缩进作为数组索引将键插入到数组中.
keys[indent] = key;
Run Code Online (Sandbox Code Playgroud)
因为此数组包含来自前一行的键,所以删除任何缩进级别大于当前行的缩进级别的键:
for (i in keys) {if (i > indent) {delete keys[i]}}
Run Code Online (Sandbox Code Playgroud)
这使得包含密钥链的密钥数组从缩进级别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);
Run Code Online (Sandbox Code Playgroud)
第一个语句输出命令以将值分配给以键命名的关联数组元素,第二个语句输出命令以将键添加到集合的空格分隔keys
列表:
<current_collection>[<key>]="<value>";
<current_collection>[keys]+=" <key>";
Run Code Online (Sandbox Code Playgroud)
当一个键没有值时,会启动一个新的集合,如下所示:
printf("%s%s[children]+=\" %s%s\";\n", prefix, parent_key , root_prefix, key);
printf("declare -g -A %s%s;\n", root_prefix, key);
Run Code Online (Sandbox Code Playgroud)
第一个语句输出命令以将新集合添加到当前集合的空格分隔children
列表中,第二个语句输出命令以声明新集合的新关联数组:
<current_collection>[children]+=" <new_collection>"
declare -g -A <new_collection>;
Run Code Online (Sandbox Code Playgroud)
所有输出yay_parse
都可以通过bash eval
或source
内置命令解析为bash 命令.
这是Stefan Farestam的答案的扩展版本:
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);
}
}'
}
Run Code Online (Sandbox Code Playgroud)
此版本支持-
字典和列表的表示法以及简称。以下输入:
global:
input:
- "main.c"
- "main.h"
flags: [ "-O3", "-fpic" ]
sample_input:
- { property1: value, property2: "value2" }
- { property1: "value3", property2: 'value 4' }
Run Code Online (Sandbox Code Playgroud)
产生以下输出:
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"
Run Code Online (Sandbox Code Playgroud)
如您所见,这些-
项目会自动编号,以便为每个项目获得不同的变量名。由于bash
没有多维数组,因此这是一种变通方法。支持多个级别。要解决@briceburg提到的尾随空格的问题,应将值用单引号或双引号引起来。但是,仍然存在一些局限性:当值包含逗号时,字典和列表的扩展会产生错误的结果。此外,还不支持更复杂的结构,如跨越多行的值(如ssh-keys)。
关于代码的几句话:第一个sed
命令将字典的缩写形式扩展{ key: value, ...}
为正则,并将其转换为更简单的yaml样式。第二个sed
调用对列表的缩写表示法相同,并转换[ entry, ... ]
为带有该-
表示法的逐项列表。第三个sed
调用是处理普通字典的原始调用,现在添加了带有-
和缩进的句柄列表。该awk
部分为每个缩进级别引入一个索引,并在变量名称为空时(即在处理列表时)增加索引。使用计数器的当前值代替空的vname。当上升一级时,计数器清零。
编辑:我为此创建了一个github存储库。
我曾经使用 python 将 yaml 转换为 json 并在 jq 中进行处理。
python -c "import yaml; import json; from pathlib import Path; print(json.dumps(yaml.safe_load(Path('file.yml').read_text())))" | jq '.'
Run Code Online (Sandbox Code Playgroud)
小智 5
perl -ne 'chomp; printf qq/%s="%s"\n/, split(/\s*:\s*/,$_,2)' file.yml > file.sh
Run Code Online (Sandbox Code Playgroud)