如何将 yaml 文件读入 bash 关联数组?

How to read yaml file into bash associative array?

我想把一个yaml文件的内容读入bash关联数组,这是一个简单的键值映射。

示例map.yaml

---

a: "2"
b: "3"
api_key: "somekey:thatcancontainany@chara$$ter"

密钥可以包含除 space 之外的任何字符 该值可以包含不受限制的任何字符 $!:=@etc

永远不变的是键和值之间的分隔符是:

剧本proceses.sh

#!/usr/bin/env bash

declare -A map
# how to read here into map variable, from map.yml file
#map=populatesomehowfrommap.yaml

for key in "${!map[@]}"
do
  echo "key  : $key"
  echo "value: ${map[$key]}"
done

我尝试使用 yq tool,类似于 json 工具 jq,但还没有成功。

一种方法是让 在一行中输出每个 key/value 对,语法如下:

key@value

然后我们可以使用bash's IFS来拆分这些值。
@只是一个例子,可以用任何单个字符替换


这可行,但请注意以下限制

  • 它不期望嵌套值,只有一个平面列表`
  • 字段分隔符(示例中的@)在 YAML key/value 的
  • 中不存在

#!/bin/bash

declare -A arr
while IFS="@" read -r key value
do
    arr[$key]="$value"
done < <(yq e 'to_entries | .[] | (.key + "@" + .value)' input.yaml)

for key in "${!arr[@]}"
do
    echo "key  : $key"
    echo "value: ${arr[$key]}"
done
$ cat input.yaml
---
a: "bar"
b: "foo"

$
$
$ ./script.sh
key  : a
value: bar
key  : b
value: foo
$

具有以下限制:

  • 单行的简单 YAML key: "value"
  • 密钥可以不能包含:
  • 值总是包含在 "
#!/usr/bin/env bash

declare -A map
regex='^([^:]+):[[:space:]]+"(.*)"[[:space:]]*$'

while IFS='' read -r line
do
    if [[ $line =~ $regex ]]
    then
        printf -v map["${BASH_REMATCH[1]}"] '%b' "${BASH_REMATCH[2]}"
    else
        echo "skipping: $line" 1>&2
    fi
done < map.yaml

更新

这是一个使用 yq 的可靠解决方案,如果内置 @tsv 过滤器实施无损 TSV 转义规则而不是 CSV 规则,该解决方案会更简单。

#!/usr/bin/env bash

declare -A map

while IFS=$'\t' read key value
do
    printf -v map["$key"] '%b' "$value"
done < <(
    yq e '
        to_entries | .[] |
        [
            (.key   | sub("\","\") | sub("\n","\n") | sub("\r","\r") | sub("\t","\t")),
            (.value | sub("\","\") | sub("\n","\n") | sub("\r","\r") | sub("\t","\t"))
        ] |
        join("  ")
    ' map.yaml
)

注意: join 需要文字 Tab

我使用了@Fravadona 的答案所以将其标记为答案

对我的用例进行一些修改后,对我有用的看起来像:

DEFS_PATH="definitions"

declare -A ssmMap
for file in ${DEFS_PATH}/*
do
    filename=$(basename -- "$file")
    projectName="${filename%.*}"

    regex='^([^:]+):[[:space:]]*"(.*)"[[:space:]]*$'
    while IFS='' read -r line
    do
        if [[ $line =~ $regex ]]
        then
            value="${BASH_REMATCH[2]}"
            value=${value//"{{ ssm_env }}"/$INFRA_ENV}
            value=${value//"{{ ssm_reg }}"/$SSM_REGION}
            value=${value//"{{ projectName }}"/$projectName}
            printf -v ssmMap["${BASH_REMATCH[1]}"] '%b' "$value"
        else
            echo "skipping: $line" 1>&2
        fi
    done < "$file"
done

基本上在实际用例中,我有一个文件夹,其中包含 yaml 定义。我遍历所有这些以形成关联数组 ssmMap