6

I would like to use some kind of minimalistic template engine with pure bash and envsubst:

user@host:~$ env -i FOO=foo BAR="bar baz" envsubst '$FOO,$BAR' \
  <<< 'Hello "$FOO" and "$BAR"!'
Hello "foo" and "bar baz"!

Above works, but only contains static variables.

Now let's assume environment variables are given dynamically, like with an associative array:

declare -A MY_ENV=([FOO]=foo [BAR]="bar baz")

Parsing the array key-value pairs only works for environment values without whitespaces (errors):

env -i \
  $(for k in "${!MY_ENV[@]}"; do printf "%s=%s " $k "${MY_ENV[$k]}"; done) \ 
  envsubst #...

Trying to wrap environment values by quotes (note '%s' instead of %s ) also errors:

env -i \
  $(for k in "${!MY_ENV[@]}"; do printf "%s='%s' " $k "${MY_ENV[$k]}"; done) \ 
  envsubst #...

Output of set -x: Reason: set -x shows that the argument for env becomes one giant string:

+ env -i 'FOO='\''foo'\''' 'BAR='\''bar' 'baz'\''' envsubst #...
env: ‘baz'’: No such file or directory

I must have missed a shell escape lession (again...). How might I rewrite last example to work properly?

2 Answers2

4

This is [BashFAQ/050] -- you have to use an array to ensure each "key=value" pair is properly quoted.

vars=()
for k in "${!MY_ENV[@]}"; do
    vars+=( "$k=${MY_ENV[$k]}" )
done
env -i "${vars[@]}" envsubst '$FOO,$BAR' <<< 'Hello "$FOO" and "$BAR"!'
Hello "foo" and "bar baz"!

Notice how I'm not injecting any extra quote characters.


Extending to so we don't have to hard-code the variable names:

keys=( "${!MY_ENV[@]}" )
printf -v varnames ',%s' "${keys[@]/#/'$'}"

env -i "${vars[@]}" envsubst "${varnames#,}" <<< 'Hello "$FOO" and "$BAR"!'

# or without the `varnames` temp var
env -i "${vars[@]}" envsubst "$(IFS=,; echo "${keys[*]/#/'$'}")" <<< 'Hello "$FOO" and "$BAR"!'
glenn jackman
  • 84,176
  • 15
  • 116
  • 168
  • 1
    Fantastic! Learned a lot, thank you. After reading [Shell parameter expansion](https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html), solution became clear. – user00705358365913 Jul 22 '22 at 20:20
0

A workaround is to export each key-value pair one-by-one and execute envsubst afterwards:

for k in "${!MY_ENV[@]}"; do export $k="${MY_ENV[$k]}"; done 
envsubst '$FOO,$BAR' <<< 'Hello "$FOO" and "$BAR"!'
#Hello "foo" and "bar baz"!

The code might be placed inside a subshell to prevent polluting the current environment.


(PS: Still curious, why dynamically building arguments after env in original post did not work - any help appreciated.)