1

I stumbled onto the question Replacing only specific variables with envsubst and in one of the comments was this shell example:

envsubst "$(printf '${%s} ' ${!PREFIX*})" < infile

See the original question for the full background, but in essence it is telling envsubst to replace only those environment variables that begin with the string "PREFIX".

But how is this working? Can someone explain the different "steps" that are involved? Is the infile read first? And what part is doing the grep/find kind of thing, so it can use the input from the file? Is the argument "${!PREFIX*}" some kind of regex? And is there some sub-shell magic being done here?

In order to try understanding it better, I created my own infile with this content:

1: ${VAR1}
2: ${VAR2}
3: ${VAR3}
4: ${PREFIX_1}
5: ${PREFIX_2}
6: ${PREFIX_3} ?
7: ${PREFIX_4} + ${PREFIX_5}
8: ${VAR4}
9: bla bla bla

I then replaced the envsubst with echo, resulting in this line:

echo "$(printf '${%s} ' ${!PREFIX*})" < infile

And this result:

${PREFIX_1}

So it works, but only for the first match. What am I doing wrong? I expected the output to contain all the "PREFIX_" variables 1 to 5. Maybe I messed up when I replaced envsubst with echo? If so, how can I see what stuff is sent to envsubst?

  • To see what it does, try echo ${!PREFIX*} and printf '${%s} ' ${!PREFIX*}, then e.g. echo ${!P*} and printf '${%s} ' ${!P*}. – Bodo Jul 22 '19 at 11:27

1 Answers1

3

${!prefix_*} is a Bash feature that expands to the names of all shell variables that start with prefix_. The printf in the command substitution then prints them inside braces (it repeats the format string as many times as necessary).

$ prefix_foo=1 prefix_bar=2
$ printf '${%s}\n' ${!prefix_*}
${prefix_bar}
${prefix_foo}

Because of the command substitution, they get dropped on envsubst's command line. There's word splitting involved in both the ${!prefix_*} expansion and the result of the command substitution, but it doesn't matter with the default IFS since variable names can't contain whitespace or glob characters.

echo "$(printf '${%s} ' ${!PREFIX*})" < infile

Here, the contents of infile don't matter, since echo doesn't read anything from stdin. That will just print the names of the variables starting with PREFIX.

ilkkachu
  • 138,973
  • @steeldriver, yes, exactly. – ilkkachu Jul 22 '19 at 12:25
  • Great answer! This explains why I got the PREFIX_1 one printed, because I had set it as an environment variable in an earlier test (that I had forgotten about). I thought that the only thing that did anything regarding environment variables was the envsubst command, and by replacing it with echo I thought it only used the file as input, not environment variables. – j5423951 Jul 22 '19 at 12:39