Shell scripting

Associative arrays in non-bash shells

Bash 4 introduced the ability to declare and deal with associative arrays in a very pythonistic way, like so:

declare -A some_array        # create the array
some_array[key] = 1          # put value
echo ${some_array[key]}      # get value 

Hooray! However, depending on the context, bash 4 or even older bash might not be available (this is typically the case with alpine based docker images). Here is an example based on database provisioning with pure sh. Say you would like to create one database per user with their respective password (which is definitely calling for associative array). You would like to be able to do it this way in your docker-compose.yml

  postgres:
    image: postgres:12.1-alpine
    restart: always
    volumes:
      - ./postgres_init:/docker-entrypoint-initdb.d
    environment:
      - POSTGRES_USER=P0stgr3sSup3rUs3r
      - POSTGRES_PASSWORD=SomeAdminPassword
      - POSTGRES_DBS=some_user->some_password|some_other_user->some_other_password

Here is what you need in your entrypoint.sh

#!/bin/sh

if [ -n "$POSTGRES_DBS" ]; then
    IFS=\|
    for s in $POSTGRES_DBS ; do
        pass=$(echo "$s" | sed 's/.*->//');
        user=$(echo "$s" | sed 's/->.*//');
        db=$user"_db";

        psql --username "$POSTGRES_USER" <<-EOSQL
            CREATE USER $user WITH PASSWORD '$pass';
            CREATE DATABASE $db;
            REVOKE CONNECT ON DATABASE $db FROM PUBLIC;
            GRANT ALL PRIVILEGES ON DATABASE $db TO $user;
            ALTER DATABASE $db OWNER TO $user;
        EOSQL
    done
fi

We first set the Internal Field Separator IFS to | to ensure the first split to loop on. The second split level is made with sed, first tacking the substring before ->, then the one after.