我希望提供一个结构化的配置文件,尽可能方便非技术用户的编辑(不幸的是,它必须是一个文件),所以我想使用YAML。然而,我找不到任何从Unix shell脚本中解析的方法。
如何从Linux shell脚本中解析YAML文件?
yq
在shell中读/写yaml文件。该项目页面在这里。
mikefarah.github.io/yq
你可以用
brew
、
apt
安装该工具,或者下载二进制文件。读取一个值就像
yq r some.yaml key.value
一样简单
json == yaml
,但是
yaml != json
。这意味着yaml是json的超集。
这里有一个只用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
will output:
global_debug="yes"
global_verbose="no"
global_debugging_detailed="no"
global_debugging_header="debugging started"
output_file="yes"
它也能理解由ruby生成的yaml文件,其中可能包括ruby符号,如。
:global:
:debug: 'yes'
:verbose: 'no'
:debugging:
:detailed: 'no'
:header: debugging started
:output: 'yes'
并将输出与前面的例子中相同的内容。
脚本中的典型用法是。
eval $(parse_yaml sample.yml)
parse_yaml接受一个前缀参数,以便导入的设置都有一个共同的前缀(这将减少命名空间碰撞的风险)。
parse_yaml sample.yml "CONF_"
yields:
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)
很好,Stefan!如果它能把yaml的
-
符号也变成原生的bash数组,那就太棒了
如果你改变awk脚本中的printf行,这应该是很容易做到的。不过要注意的是,bash不支持多维关联数组,所以你最终要用一个数组+每个值的一个键。嗯,也许应该把这个移到github上...
这需要标准的yml缩进,即2个空格。如果你使用4个空格,那么变量将得到两个下划线作为分隔符,例如:
global__debug
而不是
global_debug
。
Hi vaab - 我相信你是对的,很多读者都想从shell中解析真正的YAML文件,但不太清楚(至少对我来说)结果会是什么。通过这个脚本,我对这个问题做了一个尝试,并定义了一个子集,它对标准变量有一个合理的映射。当然,对于解析真正的YAML文件这一更大的问题,并没有任何借口。
sattu
:
它只在屏幕上打印输出。你以后如何访问这些值呢?
vaab
发布于
2020-01-09
0
人赞同
我用python编写了
shyaml
,以满足YAML在shell命令行的查询需求。
Overview:
$ 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
基本查询。
$ 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'"
RECEIVED: '1.1'
RECEIVED: '- first
- second
- third'
RECEIVED: '2'
RECEIVED: 'Valentin Lab'
RECEIVED: 'Multiline description:
Line 1
Line 2'
几个关键点。
all YAML types and syntax oddities are correctly handled, as multiline, quoted strings, inline sequences...
\0
padded output is available for solid multiline entry manipulation.
simple dotted notation to select sub-values (ie: subvalue.maintainer
is a valid key).
access by index is provided to sequences (ie: subvalue.things.-1
is the last element of the subvalue.things
sequence.)
access to all sequence/structs elements in one go for use in bash loops.
you can output whole subpart of a YAML file as ... YAML, which blend well for further manipulations with shyaml.
更多的样本和文件可在shyaml github页面 or the shyaml PYPI页面.
bmaupin
发布于
2020-01-09
0
人赞同
yq
是一个轻量级和可移植的命令行YAML处理器
该项目的目的是要成为
jq
或yaml文件的sed。
(
https://github.com/mikefarah/yq#readme
)
作为一个例子(直接从文档中窃取),给定一个sample.yaml文件为。
item1:
cats: bananas
item2:
cats: apples
yq eval '.bob.*.cats' sample.yaml
will output
- bananas
- apples
Antonin
:
它只是缺乏过滤能力
formulae.brew.sh/formula/yq
在过去的一年里,有26,679个安装。
bmaupin
:
@Antonin 我不确定这是否是你的意思,但看起来它现在有一些过滤功能。
mikefarah.gitbook.io/yq/usage/path-expressions
有什么想法,我们如何在bash脚本中使用这个?比如说把if设置为var,实际上是写到了yaml文件里 🤷
Curtis Blackwell
发布于
2020-01-09
0
人赞同
我的用例可能与这个原帖所问的不太一样,但绝对相似。
我需要把一些YAML作为bash变量拉进来。YAML 的深度不会超过一层。
YAML看起来是这样的。
KEY: value
ANOTHER_KEY: another_value
OH_MY_SO_MANY_KEYS: yet_another_value
LAST_KEY: last_value
输出像一个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
finds :
and replaces it with ="
, while ignoring ://
(for URLs)
s/$/"/g
appends "
to the end of each line
s/ *=/=/g
removes all spaces before =
我不知道你想说什么,但如果你的意思是这并不适合所有的YAML,你是对的。这就是为什么我在开始时提出了一些限制条件。我只是分享了对我的用例有效的方法,因为它比当时的任何其他方法都更能回答问题。这绝对可以扩展。
对代码注入也有点开放,但正如你所说的是一个进步。
我只写过本地使用的shell脚本,所以这对我来说不是一个问题。然而,如果你知道如何保护它和/或愿意详细说明,我一定会感激不尽。
一级深的yaml有很多形式--值可以被分割到下面的缩进行;值可以用多种方式引用,shell不会解析;所有东西都可以用大括号写在一行。
{KEY: 'value', ...}
;可能还有其他的。 最重要的是,如果你打算将结果作为shell代码来评估,那将是非常不安全的。
fbicknel
:
如果你的yaml文件顶部有
---
,请使用这个表达式来删除它。
sed -e '/^---$/d;s/:[^:\/\/]/="/g;s/$/"/g;s/ *=/=/g'
。
/^---$/d;...
是额外的位。
Torsten Bronger
发布于
2020-01-09
0
人赞同
鉴于现在Python3和PyYAML是相当容易满足的依赖关系,以下内容可能会有所帮助。
yaml() {
python3 -c "import yaml;print(yaml.safe_load(open('$1'))$2)"
VALUE=$(yaml ~/my_yaml_file.yaml "['a_key']")
rsaw
:
我喜欢Shyaml,但在断开连接的系统上,这是一个救命稻草。它应该也适用于绝大多数的python2,例如RHEL。
也许使用
yaml.safe_load
,因为它更安全。
pyyaml.org/wiki/PyAMLDocumentation
Akhil
:
被低估的答案
这很好。我做了一个经过调整的版本,将数组作为每行的一个项目来打印。替换代码0
Rafael
发布于
2020-01-09
0
人赞同
有可能将一个小脚本传递给一些解释器,比如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"
,其中data
是一个哈希(或数组),包含来自yaml的值。
作为奖励,它可以解析杰基尔的前言 just fine.
ruby -ryaml -e "puts YAML::load(open(ARGV.first).read)['tags']" example.md
Znik
:
你已经用echo把yaml放到ruby解释器中了,但是在bash脚本的其他部分应该如何使用这个变量呢?
Rafael
:
是的,它是可以使用的。
RUBY_SCRIPT
变量是一个ruby脚本,可以代替它写入文件(用
ruby -ryaml <rubyscript_filename>
运行)。它包含将输入文本转化为一些输出文本的逻辑,在内部将内容存储到
data
变量中。echo输出一个yaml文本,但你可以用
cat <yaml_filename>
来代替管道文件的内容。
Znik
:
很抱歉,我在上面的例子中没有看到这一点。首先,变量RUBY_SCRIPT保留了ruby解释器的代码。接下来echo -e模拟任何yaml数据,这是通过堆栈重定向到ruby解释器。这就像内联脚本一样调用ruby代码,最后打印到输出例子'a'和'b'变量。那么剩下的可执行代码在哪里加载到bash中的变量呢?我只看到一个解决方法:把ruby outout放到temporary_file中,其中应该有几行:variable='value',然后通过'.temporary_file'把它加载到bash中。但这只是解决方法,并没有解决。
vaab
:
@Znik 一旦你在stdout上得到了由stdin输入的东西产生的东西,剩下的就靠bash代码员的手了(作为提醒,如果你需要将
stdout
输入变量,你不必依赖临时文件!)。使用
x=$(...)
甚至是
read a b c < <(...)
)。所以,当你清楚地知道你想在YAML文件中获取什么,并且知道如何编写ruby行来访问这些数据时,这是一个有效的解决方案。即使它很粗糙,它也是这个想法的一个完整的概念证明,IMHO。不过,它确实没有为你提供一个完整的bash抽象概念。
Znik
:
Yes it is. You're rigt. Thak you for that trick. Using one variable is simple. but many wariables aren't. trick with read variable list < <(execution to stdout) is very usefull :)
Martin Hecht
发布于
2020-01-09
0
人赞同
这里是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);
该版本支持-
符号以及字典和列表的简短符号。以下的输入。
global:
input:
- "main.c"
- "main.h"
flags: [ "-O3", "-fpic" ]
sample_input:
- { property1: value, property2: "value2" }
- { property1: "value3", property2: 'value 4' }
produces this output:
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-keys),(目前)不支持。
关于代码的几句话。第一个sed
命令将字典的简短形式{ key: value, ...}
扩展为正则,并将其转换为更简单的yaml风格。第二个sed
调用对列表的简短符号做了同样的处理,并将[ entry, ... ]
转换为具有-
符号的项目化列表。第三个sed
调用是原来处理正常字典的调用,现在增加了处理带有-
和缩进的列表。替换代码13】部分为每个缩进级别引入了一个索引,并在变量名称为空时(即处理列表时)增加该索引。计数器的当前值被用来代替空的vname。当上升一级时,计数器被清零。
编辑:我已经创建了一个github repository for this.
Inian
发布于
2020-01-09
0
人赞同
将我的答案从
如何在bash中把json响应转换为yaml?
,因为这似乎是关于从命令行处理YAML文本解析的权威性帖子。
我想补充关于
yq
的细节实现的细节。由于这个YAML解析器有两个实现,都叫
yq
,如果不看实现的DSL,就很难区分哪一个在使用。这两个可用的实现是
kislyuk/yq
- The more often talked about version, which is a wrapper over
jq
, written in Python using the PyYAML library for YAML parsing
mikefarah/yq
- A Go implementation, with its own dynamic DSL using the go-yaml v3 parser.
两者都可以通过标准的安装包管理器在几乎所有的主要发行版上进行安装。
kislyuk/yq -
Installation instructions
mikefarah/yq -
Installation instructions
这两个版本都有一些优点和缺点,但有几个有效的要点需要强调(从他们的repo说明中采用)。
kislyuk/yq
Since the DSL is the adopted completely from
jq
, for users familiar with the latter, the parsing and manipulation becomes quite straightforward
Supports mode to
preserve YAML tags and styles
, but loses comments during the conversion. Since
jq
doesn't preserve comments
, during the round-trip conversion, the comments are lost.
As part of the package,
XML support
is built in. An executable,
xq
, which transcodes XML to JSON using
xmltodict
and pipes it to
jq
, on which you can apply the same DSL to perform CRUD operations on the objects and round-trip the output back to XML.
Supports in-place edit mode with
-i
flag (similar to
sed -i
)
mikefarah/yq
Prone to frequent changes in DSL,
migration from 2.x - 3.x
Rich support for anchors, styles and tags. But lookout for bugs once in a while
A relatively simple
Path expression
syntax to navigate and match yaml nodes
Supports YAML->JSON, JSON->YAML formatting and pretty printing YAML (with comments)
Supports in-place edit mode with
-i
flag (similar to
sed -i
)
Supports coloring the output YAML with
-C
flag (not applicable for JSON output) and indentation of the sub elements (default at 2 spaces)
Supports Shell completion for most shells - Bash, zsh (because of powerful support from
spf13/cobra
used to generate CLI flags)
我对以下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
两种实现都要进行的各种操作(一些经常使用的操作)。
Modifying node value at root level - Change value of root_key2
Modifying array contents, adding value - Add property to coffee
Modifying array contents, deleting value - Delete property from orange_juice
Printing key/value pairs with paths - For all items under food
Using kislyuk/yq
yq -y '.root_key2 |= "this is a new value"' yaml
yq -y '.drink.coffee += { time: "always"}' yaml
yq -y 'del(.drink.orange_juice.colour)' yaml
yq -r '.food|paths(scalars) as $p | [($p|join(".")), (getpath($p)|tojson)] | @tsv' yaml
这是很直接的。你所需要的是将jq
转码为JSON输出到YAML的-y
标志。
使用mikefarah/yq
yq w yaml root_key2 "this is a new value"
yq w yaml drink.coffee.time "always"
yq d yaml drink.orange_juice.colour
yq r yaml --printMode pv "food.**"
截止到今天2020年12月21日,yq
v4已经进入测试阶段,支持更强大的路径表达,支持类似于使用jq
的DSL。请阅读过渡说明---从V3升级
starfry
发布于
2020-01-09
0
人赞同
我刚刚写了一个分析器,我称之为
耶!
(
Yaml不是山里人!
),它解析了
山里人
是YAML的一个小子集。所以,如果你想为Bash寻找一个100%兼容的YAML解析器,那么这不是它。然而,引用OP的话来说,如果你想
一个结构化的配置文件,尽可能让非技术用户容易编辑
这可能会引起人们的兴趣,因为它是类似YAML的。
It's
受到先前答案的启发
但写关联数组(
是的,它需要Bash 4.x
)而不是基本变量。它这样做的方式是允许在不事先了解键的情况下对数据进行解析,这样就可以编写数据驱动的代码。
除了键/值数组元素,每个数组都有一个包含键名列表的
keys
数组,一个包含子数组名称的
children
数组和一个指向其父数组的
parent
键。
This
is an example of 山里人:
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
Here是一个展示如何使用它的例子。
#!/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)
echo "$2$k = $(value $1 $k)"
for c in $(value $1 children)
echo -e "$2$c\n$2{"
print_collection $c " $2"
echo "$2}"
yay example
print_collection example
which outputs:
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
而且here是解析器。
yay_parse() {
# find input file
for f in "$1" "$1.yay" "$1.yml"
[[ -f "$f" ]] && input="$f" && break
[[ -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 "$@"); }
在链接的源文件中有一些文档,下面是对代码作用的简短解释。
替换代码7】函数首先定位input
文件或以退出状态1退出。接下来,它确定数据集prefix
,可以是明确指定的,也可以是从文件名推导出来的。
它将有效的bash
命令写入其标准输出,这些命令如果被执行,将定义代表输入数据文件内容的数组。其中第一条定义了顶层数组。
echo "declare -g -A $prefix;"
注意,数组声明是关联性的(-A
),这是Bash第4版的一个特点。声明也是全局性的(-g
),所以它们可以在一个函数中执行,但像yay
的助手一样可以在全局范围内使用。
yay() { eval $(yay_parse "$@"); }
The input data is initially processed with sed
. It drops lines that don't match the 山里人 format specification before delimiting the valid 山里人 fields with an ASCII 文件分离器字符,并删除值域周围的任何双引号。
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" |
这两个表达式是相似的;它们的区别只是因为第一个表达式挑出有引号的值,而第二个表达式挑出无引号的值。
The 文件分离器(28/hex 12/octal 034)被使用,因为作为一个不可打印的字符,它不太可能出现在输入数据中。
其结果被输送到awk
中,后者每次处理其输入的一行。它使用FS字符,将每个字段分配给一个变量。
indent = length($1)/2;
key = $2;
value = $3;
所有的行都有一个缩进(可能是零)和一个键,但它们并不都有一个值。它计算出一行的缩进级别,将包含前导空白的第一个字段的长度除以2。没有任何缩进的顶层项目的缩进级别为零。
接下来,它将计算出当前项目使用的prefix
。这就是被添加到键名中的数组名。顶层数组有一个root_prefix
,它被定义为数据集名称和一个下划线。
root_prefix = "'$prefix'_";
if (indent ==0 ) {
prefix = ""; parent_key = "'$prefix'";
} else {
prefix = root_prefix; parent_key = keys[indent-1];
替换代码23】是位于当前行缩进水平之上的键,代表当前行所处的集合。该集合的键/值对将被存储在一个数组中,其名称定义为prefix
和parent_key
的连接。
对于顶层(缩进程度为零)的数据集前缀被用作父键,所以它没有前缀(被设置为""
)。所有其他数组的前缀为根前缀。
接下来,当前的键被插入到一个包含键的(awk内部的)数组中。这个数组在整个awk会话中是持续存在的,因此包含了以前的行所插入的键。键被插入到数组中,使用其缩进作为数组的索引。
keys[indent] = key;
因为这个数组包含前几行的键,任何比当前行的缩进程度大的键都会被删除。
for (i in keys) {if (i > indent) {delete keys[i]}}
这将留下包含从缩进程度为0的根部到当前行的密钥链的密钥数组。它删除了前一行比当前行缩进更深时留下的陈旧的键。
最后一节输出bash
命令:一个没有数值的输入行开始一个新的缩进级别(acollection在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
的输出都可以被basheval
或source
内置命令解析为bash命令。
你是否考虑过将其作为GitHub上的一个项目?或者已经是了?
starfry
:
@daniel,它在GitHub上,但不是在它自己的repo中--你可以在下面找到它
here
.参见
examples
和
usr/lib
目录,这些在我对问题的回答中都有链接。如果有兴趣,我可以把它分成自己的 repo。
夸奖一下YAY。 起初,我把它改写成了纯粹的bash,但后来我忍不住了,把它重新实现为一个基本的分析器,支持数组和嵌套结构,不能互相踩到对方的名字。 它在
github.com/binaryphile/y2s
.
dogbane
发布于
2020-01-09
0
人赞同
很难说,因为这取决于你希望解析器从YAML文档中提取什么。对于简单的情况,你可能可以使用
grep
、
cut
、
awk
等。对于更复杂的解析,你需要使用一个完整的解析库,如Python的
ǞǞǞ
or
YAML::Perl
.
jonseymour
发布于
2020-01-09
0
人赞同
另一个选择是将YAML转换为JSON,然后使用jq与JSON表示法进行交互,或者从中提取信息,或者编辑它。
我写了一个简单的bash脚本,包含这个胶水--见
Y2J project on GitHub
mushuweasel
发布于
2020-01-09
0
人赞同
perl -ne 'chomp; printf qq/%s="%s"\n/, split(/\s*:\s*/,$_,2)' file.yml > file.sh
Znik
:
它不适用于结构化的yaml。另外,如何防止使用临时文件.sh?
Rick van der Zwet
发布于
2020-01-09
0
人赞同
vdegenne
发布于
2020-01-09
0
人赞同
我知道这很具体,但我认为我的答案对某些用户可能有帮助。
如果你的机器上安装了
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字符串字面。然后你可以在你的unix系统中的任何json分析器中使用该字符串。
gek
发布于
2020-01-09
0
人赞同
现在做这件事的一个快速方法
(以前的对我不起作用)。
sudo wget https://github.com/mikefarah/yq/releases/download/v4.4.1/yq_linux_amd64 -O /usr/bin/yq &&\
sudo chmod +x /usr/bin/yq
例子 asd.yaml:
a_list:
- key1: value1
key2: value2
key3: value3
user@vm:~$ yq e '.' asd.yaml
a_list:
- key1: value1
key2: value2
key3: value3
解析key3。
user@vm:~$ yq e '.a_list[0].key3' asd.yaml
value3
Raj kamal
发布于
2020-01-09
0
人赞同
我曾经用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 '.'
cangers
发布于
2020-01-09
0
人赞同
如果你有python 2和PyYAML,你可以使用我写的这个分析器,叫做
parse_yaml.py
.它所做的一些整洁的事情是让你选择一个前缀(以防你有多个具有类似变量的文件)并从一个yaml文件中挑选一个单一的值。
例如,如果你有这些yaml文件。
staging.yaml。
type: sqllite
host: 127.0.0.1
user: dev
password: password123
prod.yaml。
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
030
发布于
2020-01-09
0
人赞同
seWilliam
发布于
2020-01-09
0
人赞同
当你需要一个 "如何从shell脚本中处理YAML/JSON/兼容数据 "的解决方案时,可以考虑在几乎所有使用Python的操作系统(*nix、OSX、Windows)上运行。
yamlpath
它提供了几个命令行工具,用于读取、写入、搜索和合并YAML、EYAML、JSON和兼容文件。 由于几乎所有的操作系统都预装了Python,或者安装起来很容易,这使得yamlpath具有很强的可移植性。 更有趣的是:这个项目定义了一种直观的路径语言,具有非常强大的、方便命令行的语法,可以访问一个
or more
nodes.
对于你的具体问题,在用以下方法安装了yamlpath之后
Python的本地软件包管理器
或你的操作系统的软件包管理器(yamlpath通过RPM提供给某些操作系统)。
#!/bin/bash
# Read values directly from YAML (or EYAML, JSON, etc) for use in this shell script:
myShellVar=$(yaml-get --query=any.path.no[matter%how].complex source-file.yaml)
# Use the value any way you need:
echo "Retrieved ${myShellVar}"
# Perhaps change the value and write it back:
myShellVar="New Value"
yaml-set --change=/any/path/no[matter%how]/complex --value="$myShellVar" source-file.yaml
不过你没有指定数据是一个简单的标量值,所以让我们提高一下难度。 如果你想要的结果是一个数组呢? 更具挑战性的是,如果它是一个哈希数组,而你只想要每个结果的一个属性呢? 进一步假设,你的数据实际上是分布在multipleYAML文件,并且你需要在一个单一的查询中获得所有的结果。 这是个更有趣的问题,可以用它来演示。 所以,假设你有这两个YAML文件。
File: data1.yaml
baubles:
- name: Doohickey
sku: 0-000-1
price: 4.75
weight: 2.7g
- name: Doodad
sku: 0-000-2
price: 10.5
weight: 5g
- name: Oddball
sku: 0-000-3
price: 25.99
weight: 25kg
File: data2.yaml
baubles:
- name: Fob
sku: 0-000-4
price: 0.99
weight: 18mg
- name: Doohickey
price: 10.5
- name: Oddball
sku: 0-000-3
description: This ball is odd
在将data2.yaml的变化应用到data1.yaml之后,你如何只报告库存中每个项目的sku
,所有这些都来自shell脚本? 试试这个。
#!/bin/bash
baubleSKUs=($(yaml-merge --aoh=deep data1.yaml data2.yaml | yaml-get --query=/baubles/sku -))
for sku in "${baubleSKUs[@]}"; do
echo "Found bauble SKU: ${sku}"
你只需通过几行代码就能得到你所需要的东西。
Found bauble SKU: 0-000-1
Found bauble SKU: 0-000-2
Found bauble SKU: 0-000-3
Found bauble SKU: 0-000-4
正如你所看到的,yamlpath把非常复杂的问题变成了微不足道的解决方案。 请注意,整个查询是作为一个流处理的;查询没有改变YAML文件,也没有临时文件。
我意识到这是 "又一个解决相同问题的工具",但在阅读了这里的其他答案后,yamlpath似乎比大多数替代品更便携、更强大。 它还能完全理解YAML/JSON/兼容文件,而且它可以not需要将YAML转换为JSON来执行要求的操作。 因此,只要你需要改变源YAML文件中的数据,原始YAML文件中的注释就会被保留下来。 像一些替代品一样,yamlpath也可以跨操作系统移植。 更重要的是,yamlpath定义了一种非常强大的查询语言,可以实现非常专业/过滤的数据查询。 它甚至可以在一个单一的查询中对文件中不同部分的结果进行操作。
如果你想一次获得或设置数据中的许多值--包括哈希/数组/图/列表这样的复杂数据--yamlpath可以做到。 如果你想要一个值,但不知道它在文件中的确切位置,yamlpath可以找到它并给你确切的路径。 需要将多个数据文件合并在一起,包括来自STDIN的数据文件? yamlpath也可以这样做。 此外,yamlpath完全理解YAML锚和它们的别名,总是准确地给出或改变你所期望的数据,无论它是一个具体的还是引用的值。
免责声明:我编写和维护了yamlpath,它是基于ruamel.yaml的,而ruamel.yaml又是基于PyYAML的。 因此,yamlpath是完全符合标准的。
Carson Stevens
发布于
2020-01-09
0
人赞同
复杂的解析最容易使用一个库,如Python的
ǞǞǞ
or
YAML::Perl
.
如果你想把所有YAML值解析成bash值,可以试试这个脚本。这也会处理注释。请看下面的用法示例。
# pparse.py
import yaml
import sys
def parse_yaml(yml, name=''):
if isinstance(yml, list):
for data in yml:
parse_yaml(data, name)
elif isinstance(yml, dict):
if (len(yml) == 1) and not isinstance(yml[list(yml.keys())[0]], list):
print(str(name+'_'+list(yml.keys())[0]+'='+str(yml[list(yml.keys())[0]]))[1:])
else:
for key in yml:
parse_yaml(yml[key], name+'_'+key)
if __name__=="__main__":
yml = yaml.safe_load(open(sys.argv[1]))
parse_yaml(yml)
test.yml
- folders:
- temp_folder: datasets/outputs/tmp
- keep_temp_folder: false
- MFA:
- MFA: false
- speaker_count: 1
- G2P:
- G2P: true
- G2P_model: models/MFA/G2P/english_g2p.zip
- input_folder: datasets/outputs/Youtube/ljspeech/wavs
- output_dictionary: datasets/outputs/Youtube/ljspeech/dictionary.dict
- dictionary: datasets/outputs/Youtube/ljspeech/dictionary.dict
- acoustic_model: models/MFA/acoustic/english.zip
- temp_folder: datasets/outputs/tmp
- jobs: 4
- align:
- config: configs/MFA/align.yaml
- dataset: datasets/outputs/Youtube/ljspeech/wavs
- output_folder: datasets/outputs/Youtube/ljspeech-aligned
- TTS:
- output_folder: datasets/outputs/Youtube
- preprocess:
- preprocess: true
- config: configs/TTS_preprocess.yaml # Default Config
- textgrid_folder: datasets/outputs/Youtube/ljspeech-aligned
- output_duration_folder: datasets/outputs/Youtube/durations
- sampling_rate: 44000 # Make sure sampling rate is same here as in preprocess config
需要YAML值的脚本。
yaml() {
eval $(python pparse.py "$1")
yaml "test.yml"
# What python printed to bash:
folders_temp_folder=datasets/outputs/tmp
folders_keep_temp_folder=False
MFA_MFA=False
MFA_speaker_count=1
MFA_G2P_G2P=True
MFA_G2P_G2P_model=models/MFA/G2P/english_g2p.zip
MFA_G2P_input_folder=datasets/outputs/Youtube/ljspeech/wavs
MFA_G2P_output_dictionary=datasets/outputs/Youtube/ljspeech/dictionary.dict
MFA_dictionary=datasets/outputs/Youtube/ljspeech/dictionary.dict
MFA_acoustic_model=models/MFA/acoustic/english.zip
MFA_temp_folder=datasets/outputs/tmp
MFA_jobs=4
MFA_align_config=configs/MFA/align.yaml
MFA_align_dataset=datasets/outputs/Youtube/ljspeech/wavs
MFA_align_output_folder=datasets/outputs/Youtube/ljspeech-aligned
TTS_output_folder=datasets/outputs/Youtube
TTS_preprocess_preprocess=True
TTS_preprocess_config=configs/TTS_preprocess.yaml
TTS_preprocess_textgrid_folder=datasets/outputs/Youtube/ljspeech-aligned
TTS_preprocess_output_duration_folder=datasets/outputs/Youtube/durations
TTS_preprocess_sampling_rate=44000
用bash访问变量。
echo "$TTS_preprocess_sampling_rate";
>>> 44000
EBolar
发布于
2020-01-09
0
人赞同
如果你知道你感兴趣的标签和你期望的YAML结构,那么用Bash编写一个简单的YAML解析器并不难。
在下面的例子中,解析器将一个结构化的YAML文件读入环境变量、一个数组和一个关联数组。
注意:这个解析器的复杂性与YAML文件的结构有关。 你需要为YAML文件中的每个结构化组件单独编写一个子程序。 高度结构化的YAML文件可能需要一个更复杂的方法,例如一个通用的递归解析器。
xmas.yaml文件。
# Xmas YAML example
# Values
pear-tree: partridge
turtle-doves: 2.718
french-hens: 3
# Array
calling-birds:
- huey
- dewey
- louie
- fred
# Structure
xmas-fifth-day:
calling-birds: four
french-hens: 3
golden-rings: 5
partridges:
count: 1
location: "a pear tree"
turtle-doves: two
解析器使用mapfile
将文件作为一个数组读入内存,然后循环浏览每个标签并创建环境变量。
pear-tree:
, turtle-doves:
and french-hens:
end up as simple environment variables
calling-birds:
becomes an array
The xmas-fifth-day:
structure is represented as an associative array however you could encode these as environment variables if you are not using Bash 4.0 or later.
Comments and white space are ignored.
#!/bin/bash
# -------------------------------------------------------------------
# A simple parser for the xmas.yaml file
# -------------------------------------------------------------------
# xmas.yaml tags
# # - Ignored
# - Blank lines are ignored
# --- - Initialiser for days-of-xmas
# pear-tree: partridge - a string
# turtle-doves: 2.718 - a string, no float type in Bash
# french-hens: 3 - a number
# calling-birds: - an array of strings
# - huey - calling-birds[0]
# - dewey
# - louie
# - fred
# xmas-fifth-day: - an associative array
# calling-birds: four - a string
# french-hens: 3 - a number
# golden-rings: 5 - a number
# partridges: - changes the key to partridges.xxx
# count: 1 - a number
# location: "a pear tree" - a string
# turtle-doves: two - a string
# This requires the following routines
# ParseXMAS
# parses #, ---, blank line
# unexpected tag error
# calls days-of-xmas
# days-of-xmas
# parses pear-tree, turtle-doves, french-hens
# calls calling-birds
# calls xmas-fifth-day
# calling-birds
# elements of the array
# xmas-fifth-day
# parses calling-birds, french-hens, golden-rings, turtle-doves
# calls partridges
# partridges
# parses partridges.count, partridges.location
function ParseXMAS()
# days-of-xmas
# parses pear-tree, turtle-doves, french-hens
# calls calling-birds
# calls xmas-fifth-day
function days-of-xmas()
unset PearTree TurtleDoves FrenchHens
while [ $CURRENT_ROW -lt $ROWS ]
LINE=( ${CONFIG[${CURRENT_ROW}]} )
TAG=${LINE[0]}
unset LINE[0]
VALUE="${LINE[*]}"
echo " days-of-xmas[${CURRENT_ROW}] ${TAG}=${VALUE}"
if [ "$TAG" = "pear-tree:" ]
declare -g PearTree=$VALUE
elif [ "$TAG" = "turtle-doves:" ]
declare -g TurtleDoves=$VALUE
elif [ "$TAG" = "french-hens:" ]
declare -g FrenchHens=$VALUE
elif [ "$TAG" = "calling-birds:" ]
let CURRENT_ROW=$(($CURRENT_ROW + 1))
calling-birds
continue
elif [ "$TAG" = "xmas-fifth-day:" ]
let CURRENT_ROW=$(($CURRENT_ROW + 1))
xmas-fifth-day
continue
elif [ -z "$TAG" ] || [ "$TAG" = "#" ]
# Ignore comments and blank lines
# time to bug out
break
let CURRENT_ROW=$(($CURRENT_ROW + 1))
# calling-birds
# elements of the array
function calling-birds()
unset CallingBirds
declare -ag CallingBirds
while [ $CURRENT_ROW -lt $ROWS ]
LINE=( ${CONFIG[${CURRENT_ROW}]} )
TAG=${LINE[0]}
unset LINE[0]
VALUE="${LINE[*]}"
echo " calling-birds[${CURRENT_ROW}] ${TAG}=${VALUE}"
if [ "$TAG" = "-" ]
CallingBirds[${#CallingBirds[*]}]=$VALUE
elif [ -z "$TAG" ] || [ "$TAG" = "#" ]
# Ignore comments and blank lines
# time to bug out
break
let CURRENT_ROW=$(($CURRENT_ROW + 1))
# xmas-fifth-day
# parses calling-birds, french-hens, golden-rings, turtle-doves
# calls fifth-day-partridges
function xmas-fifth-day()
unset XmasFifthDay
declare -Ag XmasFifthDay
while [ $CURRENT_ROW -lt $ROWS ]
LINE=( ${CONFIG[${CURRENT_ROW}]} )
TAG=${LINE[0]}
unset LINE[0]
VALUE="${LINE[*]}"
echo " xmas-fifth-day[${CURRENT_ROW}] ${TAG}=${VALUE}"
if [ "$TAG" = "calling-birds:" ]
XmasFifthDay[CallingBirds]=$VALUE
elif [ "$TAG" = "french-hens:" ]
XmasFifthDay[FrenchHens]=$VALUE
elif [ "$TAG" = "golden-rings:" ]
XmasFifthDay[GOLDEN-RINGS]=$VALUE
elif [ "$TAG" = "turtle-doves:" ]
XmasFifthDay[TurtleDoves]=$VALUE
elif [ "$TAG" = "partridges:" ]
let CURRENT_ROW=$(($CURRENT_ROW + 1))
partridges
continue
elif [ -z "$TAG" ] || [ "$TAG" = "#" ]
# Ignore comments and blank lines
# time to bug out
break
let CURRENT_ROW=$(($CURRENT_ROW + 1))
function partridges()
while [ $CURRENT_ROW -lt $ROWS ]
LINE=( ${CONFIG[${CURRENT_ROW}]} )
TAG=${LINE[0]}
unset LINE[0]
VALUE="${LINE[*]}"
echo " partridges[${CURRENT_ROW}] ${TAG}=${VALUE}"
if [ "$TAG" = "count:" ]
XmasFifthDay[PARTRIDGES.COUNT]=$VALUE
elif [ "$TAG" = "location:" ]
XmasFifthDay[PARTRIDGES.LOCATION]=$VALUE
elif [ -z "$TAG" ] || [ "$TAG" = "#" ]
# Ignore comments and blank lines
# time to bug out
break
let CURRENT_ROW=$(($CURRENT_ROW + 1))
# ===================================================================
# Load the configuration file
mapfile CONFIG < xmas.yaml
let ROWS=${#CONFIG[@]}
let CURRENT_ROW=0
# ---
while [ $CURRENT_ROW -lt $ROWS ]
LINE=( ${CONFIG[${CURRENT_ROW}]} )
TAG=${LINE[0]}
unset LINE[0]
VALUE="${LINE[*]}"
echo "[${CURRENT_ROW}] ${TAG}=${VALUE}"
if [ "$TAG" = "---" ]
let CURRENT_ROW=$(($CURRENT_ROW + 1))
days-of-xmas
continue
elif [ -z "$TAG" ] || [ "$TAG" = "#" ]
# Ignore comments and blank lines
echo "Unexpected tag at line $(($CURRENT_ROW + 1)): <${TAG}>={${VALUE}}"
break
let CURRENT_ROW=$(($CURRENT_ROW + 1))
echo =========================================
ParseXMAS
echo =========================================
declare -p PearTree
declare -p TurtleDoves
declare -p FrenchHens
declare -p CallingBirds
declare -p XmasFifthDay
This produces the following output
=========================================
[0] #=Xmas YAML example
[1] ---=
days-of-xmas[2] #=Values
days-of-xmas[3] pear-tree:=partridge
days-of-xmas[4] turtle-doves:=2.718
days-of-xmas[5] french-hens:=3
days-of-xmas[6] =
days-of-xmas[7] #=Array
days-of-xmas[8] calling-birds:=
calling-birds[9] -=huey
calling-birds[10] -=dewey
calling-birds[11] -=louie
calling-birds[12] -=fred
calling-birds[13] =
calling-birds[14] #=Structure
calling-birds[15] xmas-fifth-day:=
days-of-xmas[15] xmas-fifth-day:=
xmas-fifth-day[16] calling-birds:=four
xmas-fifth-day[17] french-hens:=3
xmas-fifth-day[18] golden-rings:=5
xmas-fifth-day[19] partridges:=
partridges[20] count:=1
partridges[21] location:="a pear tree"
partridges[22] turtle-doves:=two
xmas-fifth-day[22] turtle-doves:=two
=========================================
declare -- PearTree="partridge"
declare -- TurtleDoves="2.718"
declare -- FrenchHens="3"
declare -a CallingBirds=([0]="huey" [1]="dewey" [2]="louie" [3]="fred")
declare -A XmasFifthDay=([CallingBirds]="four" [PARTRIDGES.LOCATION]="\"a pear tree\"" [FrenchHens]="3" [GOLDEN-RINGS]="5" [PARTRIDGES.COUNT]="1" [TurtleDoves]="two" )
kenorb
发布于
2020-01-09
0
人赞同
你也可以考虑使用
Grunt
(The JavaScript Task Runner)。可以很容易地与shell集成。它支持读取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':
然后从shell中简单地运行grunt foo
(查看grunt --help
中的可用任务)。
更进一步,你可以用从你的任务(foo: { cmd: 'echo bar <%= foo %>' }
)传来的输入变量实现exec:foo
任务(grunt-exec
),以便以你想要的任何格式打印输出,然后用管道将其输入另一个命令。
也有类似于Grunt的工具,它叫做gulp带附加插件gulp-yaml.
Install via: 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/'))