如何从Linux shell脚本解析YAML文件?

时间:2011-02-16 09:25:38

标签: shell yaml

我希望提供一个结构化的配置文件,让非技术用户编辑尽可能简单(不幸的是它必须是一个文件),所以我想使用YAML。但是我找不到从Unix shell脚本解析这个问题的方法。

21 个答案:

答案 0 :(得分:227)

这是一个仅限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"

它还理解由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_"

的产率:

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)

答案 1 :(得分:85)

我在python中为shell命令行中的YAML查询需求编写了shyaml

概述:

$ 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.-1subvalue.things序列的最后一个元素。)
  • 一次性访问所有序列/结构元素,以便在bash循环中使用。
  • 你可以将YAML文件的整个子部分输出为... YAML,它可以很好地与shyaml进行进一步的操作。

shyaml github pageshyaml PyPI page上提供了更多示例和文档。

答案 2 :(得分:40)

我的用例可能与原始帖子提出的内容完全相同或不同,但它绝对相似。

我需要将一些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找到:并将其替换为=",而忽略://(对于网址)
  • s/$/"/g"追加到每行的末尾
  • s/ *=/=/g删除=
  • 之前的所有空格

答案 3 :(得分: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

,其中data是具有yaml值的散列(或数组)。

作为奖励,它会解析Jekyll's front matter就好了。

ruby -ryaml -e "puts YAML::load(open(ARGV.first).read)['tags']" example.md

答案 4 :(得分:13)

鉴于Python3和PyYAML是现在相当容易满足的依赖项,以下内容可能有所帮助:

yaml() {
    python3 -c "import yaml;print(yaml.load(open('$1'))$2)"
}

VALUE=$(yaml ~/my_yaml_file.yaml "['a_key']")

答案 5 :(得分:11)

很难说,因为它取决于您希望解析器从YAML文档中提取的内容。对于简单的情况,您可以使用grepcutawk等。对于更复杂的解析,您需要使用完整的解析库,例如Python {{3 }或PyYAML

答案 6 :(得分:10)

我刚刚写了一个解析器,我称之为 Yay! Yaml ain&#39; Yamlesque!)解析 Yamlesque ,一个小的YAML的子集。所以,如果您正在为Bash寻找100%兼容的YAML解析器,那么这不是它。但是,要引用OP,如果您希望结构化配置文件尽可能简单,非技术用户可以编辑类似YAML的,这可能会引起人们的兴趣。

它是inspred by the earlier answer但是写关联数组(是,它需要Bash 4.x )而不是基本变量。它的实现方式允许在不事先知道密钥的情况下解析数据,以便可以编写数据驱动的代码。

除了键/值数组元素外,每个数组都有一个包含键名列表的keys数组,一个包含子数组名称和children键的parent数组指其父母。

This是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

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)
  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
  }
}

here是解析器:

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 "$@"); }

链接的源文件中有一些文档,下面是代码所做内容的简短说明。

yay_parse函数首先找到input文件或退出,退出状态为1.接下来,它确定数据集prefix,显式指定或从文件名派生。

它将有效的bash命令写入其标准输出,如果执行该命令,则定义表示输入数据文件内容的数组。第一个定义了顶级数组:

echo "declare -g -A $prefix;"

请注意,数组声明是关联的(-A),这是Bash版本4的一个特性。声明也是全局的(-g),因此它们可以在函数中执行但可用于全局范围如yay助手:

yay() { eval $(yay_parse "$@"); }

输入数据最初使用sed处理。在使用ASCII File Separator字符分隔有效的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" |

两个表达方式相似;它们之所以不同,只是因为第一个选择了引用的值,而第二个选择了未引用的值。

使用File Separator(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];
}

parent_key是在当前行的缩进级别之上的缩进级别的键,表示当前行所属的集合。集合的键/值对将存储在一个数组中,其名称定义为prefixparent_key的串联。

对于顶级(缩进级别为零),数据集前缀用作父键,因此它没有前缀(它设置为"")。所有其他数组都以前缀为前缀。

接下来,将当前密钥插入包含密钥的(awk-internal)数组中。该数组在整个awk会话中持续存在,因此包含由前一行插入的键。使用其缩进作为数组索引将键插入到数组中。

keys[indent] = key;

因为此数组包含前一行的键,所以删除任何缩进级别大于当前行缩进级别的键:

 for (i in keys) {if (i > indent) {delete keys[i]}}

这使得包含从缩进级别0的根的键链到当前行的键数组。它删除了当前一行缩进比当前行更深时保留的陈旧键。

最后一节输出bash命令:没有值的输入行开始一个新的缩进级别(YAML用语中的集合)和带有值的输入行添加一个键到目前的集合。

该集合的名称是当前行prefixparent_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 evalsource内置命令解析为bash命令。

答案 7 :(得分:10)

  

yq是轻巧且可移植的命令行YAML处理器

     

该项目的目标是将jq或yaml文件保存起来。

http://mikefarah.github.io/yq/

作为示例(直接从documentation窃取),给出了一个sample.yaml文件:

---
bob:
  item1:
    cats: bananas
  item2:
    cats: apples

然后

yq r sample.yaml bob.*.cats

将输出

- bananas
- apples

答案 8 :(得分:7)

此处是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' }

产生以下输出:

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调用是处理普通词典的原始调用,现在添加了处理带有-和缩进的列表的功能。 awk部分为每个缩进级别引入一个索引,并在变量名称为空时(即在处理列表时)增加索引。使用计数器的当前值代替空的vname。当上升一级时,计数器将清零。

编辑:我为此创建了一个github repository

答案 9 :(得分:5)

另一种选择是将YAML转换为JSON,然后使用jq与JSON表示交互,以从中提取信息或编辑它。

我写了一个包含这个胶水的简单bash脚本 - 请参阅Y2J project on GitHub

答案 10 :(得分:5)

将我的答案从How to convert a json response into yaml in bash中移出,因为这似乎是处理从命令行解析YAML文本的权威文章。

我想添加有关yq YAML实现的详细信息。由于此YAML解析器有两种实现,两种实现都有名称yq,因此不查看实现的DSL就很难区分使用哪种。有两种可用的实现方式

  1. kislyuk/yq-讨论最多的版本,它是jq的包装,是使用PyYAML库以Python进行YAML解析的Python编写的
  2. mikefarah/yq-一个Go实现,使用go-yaml v3解析器使用自己的动态DSL。

两者都可以通过标准安装程序包管理器在几乎所有主要发行版上进行安装

  1. kislyuk / yq-Installation instructions
  2. mikefarah / yq-Installation instructions

两个版本在其他方面都有优点和缺点,但有几个要点值得强调(从其回购说明中采纳)

kislyuk / yq

  1. 由于jq完全采用了DSL,因此对于熟悉后者的用户来说,解析和操作变得非常简单
  2. 支持的模式为preserve YAML tags and styles,但在转换过程中会丢失注释。自jq doesn't preserve comments起,在往返转换期间,注释将丢失。
  3. 作为包的一部分,内置了XML support。可执行文件xq使用xmltodict将XML转换为JSON并将其通过管道传输到jq,您可以应用相同的DSL对对象执行CRUD操作,然后将输出往返返回XML。
  4. 通过-i标志(类似于sed -i)支持就地编辑模式

mikefarah / yq

  1. 为频繁更改DSL migration from 2.x - 3.x
  2. 对锚,样式和标签的丰富支持。但是偶尔要寻找错误
  3. 用于导航和匹配Yaml节点的相对简单的Path expression语法
  4. 支持YAML-> JSON,JSON-> YAML格式和漂亮的YAML打印(带有注释)
  5. 通过-i标志(类似于sed -i)支持就地编辑模式
  6. 支持使用-C标志为输出YAML着色(不适用于JSON输出)和子元素的缩进(默认为2个空格)
  7. 支持大多数Shell的Shell补全-Bash,zsh(由于spf13/cobra的强大支持,用于生成CLI标志)

我对以下两个版本的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

两种实现都需要执行各种操作(一些经常使用的操作)

  1. 在根级别修改节点值-更改root_key2的值
  2. 修改数组内容,增加价值-向coffee添加属性
  3. 修改数组内容,删除值-从orange_juice删除属性
  4. 打印带有路径的键/值对-对于food下的所有项目

使用kislyuk / yq

  1. yq -y '.root_key2 |= "this is a new value"' yaml
    
  2. yq -y '.drink.coffee += { time: "always"}' yaml
    
  3. yq -y 'del(.drink.orange_juice.colour)' yaml
    
  4. yq -r '.food|paths(scalars) as $p | [($p|join(".")), (getpath($p)|tojson)] | @tsv' yaml
    

这很简单。您所需要做的就是使用jq标志将-y JSON输出转码回YAML。

使用mikefarah / yq

  1.  yq w yaml root_key2 "this is a new value"
    
  2.  yq w yaml drink.coffee.time "always"
    
  3.  yq d yaml drink.orange_juice.colour
    
  4.  yq r yaml --printMode pv "food.**"
    

.. more待更新

答案 11 :(得分:4)

perl -ne 'chomp; printf qq/%s="%s"\n/, split(/\s*:\s*/,$_,2)' file.yml > file.sh

答案 12 :(得分:2)

现在做事情的快速方法(以前的方法对我不起作用):

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

答案 13 :(得分:1)

我知道这是非常具体的,但我认为我的回答对某些用户有帮助 如果您的计算机上安装了nodenpm,则可以使用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解析器一起使用。

答案 14 :(得分:1)

每当您需要一种适用于几乎所有使用Python的操作系统(* nix,OSX,Windows)的“如何使用Shell脚本中的YAML / JSON /兼容数据”解决方案时,请考虑yamlpath,它提供了几个用于读取,写入,搜索和合并YAML,EYAML,JSON和兼容文件的命令行工具。由于几乎每个操作系统都预装了Python或安装起来都很繁琐,因此这使yamlpath具有很高的可移植性。更有趣的是:该项目使用非常强大的命令行友好语法定义了一种直观的路径语言,该语法允许访问一个或更多节点。

对于您的特定问题,以及在使用Python's native package manager或操作系统的程序包管理器安装yamlpath之后(某些操作系统可以通过RPM使用yamlpath)

#!/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

尽管您没有指定数据是简单的Scalar值,所以让我们开始讨论。如果您想要的结果是数组怎么办?更具挑战性的是,如果它是一个哈希数组,而您只想要每个结果的一个属性,该怎么办?进一步假设您的数据实际上分散在多个 YAML文件中,并且您需要在单个查询中获得所有结果。这是一个有趣得多的问题。因此,假设您有以下两个YAML文件:

文件: 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

文件: 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?试试这个:

#!/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}"
done

仅需几行代码,您就能获得所需的一切:

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 /兼容文件,并且不需要将YAML转换为JSON即可执行请求的操作。这样,每当您需要更改源YAML文件中的数据时,原始YAML文件中的注释都会保留下来。像某些替代方案一样,yamlpath也可以跨操作系统移植。更重要的是,yamlpath定义了一种非常强大的查询语言,可以进行非常专业/过滤的数据查询。它甚至可以在单个查询中处理来自文件不同部分的结果。

如果您想一次获取或设置数据中的许多值-包括哈希/数组/地图/列表等复杂数据,yamlpath可以做到。是否需要一个值,但不确切知道它在文档中的位置? yamlpath可以找到它,并为您提供确切的路径。是否需要将多个数据文件合并在一起,包括来自STDIN的文件? yamlpath也这样做。此外,yamlpath可以完全理解YAML锚及其别名,无论是具体值还是参考值,始终提供或准确更改您期望的数据。

免责声明:我编写并维护了yamlpath,它基于ruamel.yaml,而后者又基于PyYAML。因此,yamlpath完全符合标准。

答案 15 :(得分:1)

您可以使用equivalent中的yq,该语言是用golang编写的:

./go-yg -yamlFile /home/user/dev/ansible-firefox/defaults/main.yml -key
firefox_version

返回:

62.0.3

答案 16 :(得分:1)

如果您拥有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

答案 17 :(得分:0)

如果您需要一个值,则可以使用一个工具将YAML文档转换为JSON并提供给jq,例如yq

sample.yaml的内容:

---
bob:
  item1:
    cats: bananas
  item2:
    cats: apples
  thing:
    cats: oranges

示例:

$ yq -r '.bob["thing"]["cats"]' sample.yaml 
oranges

答案 18 :(得分:0)

我知道我的回答很具体,但是如果已经安装了 PHP Symfony ,那么使用Symfony的YAML解析器将非常方便。

例如:

php -r "require '$SYMFONY_ROOT_PATH/vendor/autoload.php'; \
    var_dump(\Symfony\Component\Yaml\Yaml::parse(file_get_contents('$YAML_FILE_PATH')));"

在这里,我只是使用var_dump来输出已解析的数组,但是您当然可以做更多的事情...:)

答案 19 :(得分:0)

您还可以考虑使用Grunt(JavaScript任务运行器)。可以很容易地与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以获取可用任务)。

您还可以使用从任务(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 format的更多选项,请检查YAML site以查找可帮助您解析该格式的可用项目,库和其他资源。

其他工具:

  • Jshon

      

    解析,读取和创建JSON

答案 20 :(得分:0)

使用 Python 的 PyYAMLYAML::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