Search code examples
bashyamlassociative-array

How to read yaml file into bash associative array?


I want to read into bash associative array the content of one yaml file, which is a simple key value mapping.

Example map.yaml

---

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

the key can contain any characters excluding space the value can contain any characters without limitations $!:=@etc

What will always be constant is the separator between key and value is :

the script 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

I tried to play around with yq tool, similar to json tool jq but did not have success yet.


Solution

  • With the following limitations:

    • simple YAML key: "value" in single lines
    • keys cannot contain :
    • values are always wrapped in "
    #!/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
    

    Update

    Here's a robust solution using yq, which would be simpler if the builtin @tsv filter implemented the lossless TSV escaping rules instead of the CSV ones.

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

    note: the join needs a literal Tab