[bash] Array inside associative array?

So, i have learned a cool trick of having associative arrays in bash from here, which leads in pretty clean data structures (if you're into it)...

test.sh

#!/bin/bash

declare -A themes0=(

    # Theme
    [theme_base]='org.kde.breezedark.desktop'

    # Plasma theme
    [plasma_theme]="Test-dark"
    [color_scheme]="Test Dark Blue"

    # QT theme
    [QT_theme]="breeze"
    [QT_icons]="breeze-dark"
    [QT_cursor]="Breeze_Snow"

    # GTK theme
    [GTK_theme]="Breeze-Dark"
    [GTK_icon]="${themes0[QT_icons]}"
    [GTK_iconFallback]="${themes0[GTK_icon]}"
    [GTK_cursor]="${themes0[QT_cursor]}"
)

declare -A themes1=(

    # Theme
    [theme_base]='org.kde.breeze.desktop'

    # Plasma theme
    [plasma_theme]="Test-light"
    [color_scheme]="Test Light Blue"

    # QT theme
    [QT_theme]="breeze"
    [QT_icons]="breeze"
    [QT_cursor]="breeze_cursors"

    # GTK theme
    [GTK_theme]="Breeze"
    [GTK_icon]="${themes1[QT_icons]}"
    [GTK_iconFallback]="${themes1[GTK_icon]}"
    [GTK_cursor]="${themes1[QT_cursor]}"
)

declare -n themes

main() {

    for themes in "${!themes@}";
    do
        echo "${themes[plasma_theme]}"
        echo

        for key in "${!themes[@]}";
        do
            echo "${key}:   ${themes[$key]}"
        done

        echo
    done
}

main "$@"

Now, i'd like to know how to effectively do something like that (which sadly seems to be impossible directly in current bash):

#!/bin/bash

declare -A themes0=(

    [array_in_array]=(

        # Each line (except comments and empty ones) should be parsed as array later
        "Some/Path1/file1.test" "Some/Path1/file2.test"

        # Yep, array too
        "Some/Path2/file3.test" "Some/Path3/file4.test" "Some/Path4/file5.test"
    )
)

Thoughts / tips & tricks are welcome :slight_smile:

Thoughts

declare -A themes0=(

    [plasma_theme]="test"
    [files]="Some/Path1/file1.test Some/Path1/file2.test" 
    [QT_theme]="qt"
)

for key in "${!themes0[@]}";
do
    if [[ "${key: -1}" == "s" ]]; then  # its a array (for me) !
        echo "${key}:"
        values=(${themes0[$key]})
        for v in "${values[@]}"; do echo "  - $v"; done
    else
        echo "${key}:   ${themes0[$key]}"
    fi
done

EDIT: if datas fixed, we can use conf file json(use jq) or yaml(exemple)

1 Like

its a array (for me) !

:laughing:

Nice one, but i maybe showed a little too reduced example, my bad...

array_in_array will certainly have:

  1. Some complex stuff like that:
'"$HOME/.local/share/konsole/133Matrix.colorscheme" "General" "Opacity" $(echo "scale=2; ${opacity}/100" | bc)'
  1. All paths should certainly be quoted (since it can and will have spaces), therefore simple string to array is not really possible...And your example will fail on spaced path

usually i overcome this problem with your previous advices on my question, like that:

configBluetooth=(

    # Bluetooth disable
    '"bluedevilglobalrc" "General" "enableGlobalBluetooth" false'

    # ... etc
)

for config in "${configBluetooth[@]}";
do
    configWrite "$config"
done

But when working inside another array quoting each item with ' ' is not an option it seems...

  1. I d like to have specifically multiline style of array, like in op abstract example, so that i could treat each line as array item later with something like
declare -a "config=($(printf '%s\n' "$config"))"

Wow, that's really cool!

But sadly....

In theory, can json / yaml be declared dynamically inside bash file and then parsed?
Preferably without creating temp files (although it's easy with something like that):

cat >> $HOME/data.json<<"EOL"
...
EOL

for space separator, we can change the field separator (here # for example) :

[files]="Some/Path1/file 1.test#Some/Path1/file 2.test" 
...
IFS='#' values=(${themes0[$key]})    # or IFS="\t" ...
for v in "${values[@]}"; do echo "  - $v"; done

for me "fixed" was : you dont change list in script (remove,edit...)

Oh ok...Well i do in one place now, but it can be avoided!

Yeah, you have actually sparked some thought in me about IFS...
I'm not a big fan of IFS in general, for it's somewhat destructive behavior (in case you forget to change it after some trickery)

here we not change IFS for all script but only for the line :wink:
EDIT: as a LANG=de pacman -Qi package

2 Likes

Ok...So i figured that hacky way to achieve it :upside_down_face:

#!/bin/bash

CS_SYS="/usr/share/color-schemes"

declare -A themes0=(

    # Theme
    [theme_base]='org.kde.breezedark.desktop'

    [test]=$(echo 'This "$CS_SYS" is only a test, seriously!')

    [array_in_array]=`cat <<'EOF'
(

        # Each line should be parsed as array later
        '"${CS_USR}/Some/Path/file1.test" "Some/Path/file2.test"'

        # Yep, array too
        '"${CS_USR}/Some/Path/file3.test" "Some/Path/file4.test"'
)
EOF`

)

declare -A themes1=(

    # Theme
    [theme_base]='org.kde.breeze.desktop'
)

declare -n themes

main() {

    for themes in "${!themes@}";
    do

        for key in "${!themes[@]}";
        do

            local line=$(printf '%s\n' "${themes[$key]}" | sed '/^\s*#/d')
            local length=$(printf "$line" | sed 's/^(//' | sed 's/)$//')

            if (( ${#line} > ${#length} ));
            then

                # Parse single line config
                declare -a "arr=( $(printf "$length") )"

                echo "array: ${arr[@]}"
                echo "         items:"

                for line in "${arr[@]}";
                do

                    # Parse nested array
                    declare -a 'arr_in_arr=( $(printf "$line") )'

                    echo "         ${arr_in_arr[@]}"
                    echo
                    echo "          From: ${arr_in_arr[0]}"
                    echo "            To: ${arr_in_arr[1]}"
                    echo
                done
            else
                echo " line: $line"
            fi
        done
    done

    echo
}

main "$@"

What do you think, can i achieve same result even more elegant than EOF? :smile:
Basically here the only downside is slightly off indentation, because closing EOF must be 1st character on line...

Much more adequate version than above (i was in a hurry and posted one with escape error).

ArrayInAssociativeArray.sh

#!/bin/bash

CS_SYS="/usr/share/color-schemes"
CS_USR="$HOME/.local/share/color-schemes"

declare -A themes0=(

    # Color-scheme
    [name]='Test Dark Blue'
    [base]="${CS_SYS}/BreezeDark.colors"

    [array_in_array]=`cat <<'EOF'
(
        # Each line should be parsed as array later
        '( "${CS_USR}/Some/Path/file1.test" "Some/Path/file2.test" )'

        # Yep, array too
        '( "${CS_USR}/Some/Path/file3.test" "Some/Path/file4.test" )'
)
EOF
`

)

declare -n themes

main() {

    printf '%s\n' "${themes0[@]}"
    echo
    echo "------------------------------------------"
    echo

    for themes in "${!themes@}";
    do

        for key in "${!themes[@]}";
        do

            local item="${themes[$key]}"
            local linelen=$(printf '%s\n' "${themes[$key]}" | sed '/^\s*#/d')
            local length=$(printf "$linelen" | sed 's/^(//; s/)$//')

            if (( ${#linelen} > ${#length} ));
            then

                # Parse single line config
                declare -a "arr=$(printf '%s\n' "$item")"

                echo "array: ${arr[@]}"

                for line in "${arr[@]}";
                do

                    # Parse nested array
                    declare -a "arr_in_arr=$(printf '%s\n' "$line")"

                    echo "       item: ${arr_in_arr[@]}"
                    echo "             from: ${arr_in_arr[0]}"
                    echo "               to: ${arr_in_arr[1]}"
                    echo
                done
            else
                echo " line: $item"
            fi
        done
    done
}

main "$@"


ArrayInArray.sh

#!/bin/bash

CS_SYS="/usr/share/color-schemes"
CS_USR="$HOME/.local/share/color-schemes"

array=(

    # Name
    'Test Dark Blue'

    # Based on
    "${CS_SYS}/BreezeDark.colors"

    # Highlight
    '(
        # Old color
        "61,174,233"

        # New color
        "31,124,133"
    )'

    # Button
    '( "${CS_USR}/${local_name}.colors" "Colors:Button" "ForegroundNormal" "193,209,227" )'
)

main() {

    echo
    echo " -------------   Whole array   -------------- "
    echo
    printf '%s\n' "${array[@]}"

    echo
    echo " -------------   Line by line   ------------- "
    echo

    local local_name="BreezeTest"

    for line in "${array[@]}";
    do

        local length=$(printf "$line" | sed 's/^(//; s/)$//')

        if (( ${#line} > ${#length} ));
        then

            # Parse single line config
            declare -a "arr=$(printf '%s\n' "$line")"

            echo "array: ${arr[@]}"

            for line in "${arr[@]}";
            do
                echo "       item: $line"
            done
        else
            echo " line: $line"
        fi
    done
}

main "$@"

This way you can pretty much do anything as with usual arrays:

  • Multiline style array in array
  • Comments inside array in array
  • Global variables
  • Local variables

Forum kindly sponsored by