Replace environment variables in text if they exist

You can use env to see all currently defined environment variables and then use that list to only replace those. (The man page isn't very clear on that, but see this answer for elucidation.)

echo 'Hello $USER $UNKNOWN' | envsubst "$(env | cut -d= -f1 | sed -e 's/^/$/')"

(The output of env lists the values of the variables as well, but envsubst also wants to see a leading $, so we can't just use cut -d= -f1 on its own, unfortunately. You could use a single sed to do cut's job as well, see previous revision, but I prefer the clarity of cut over a tiny performance gain.)


It you pass an argument like $USER$PATH to envsubst, then it expands only those variables that are referenced in that argument.

So one way could be to pass it all the currently defined environment variables in that format. With zsh:

echo 'Hello $USER ${USER} $UNDEFINED_VARIABLE' |
  envsubst \$${(kj:$:)parameters[(R)*export*]}
  • $parameters is a special associative array that maps variable names to their type
  • $parameters[(R)*export*] expands to all the elements of the associative array whose value contains export.
  • with the k parameter expansion flag, the key instead of the value is returned
  • j:$: joins those elements with $ in between, and we add one at the start.

With other shells, you can always revert to perl to get that list:

echo 'Hello $USER ${USER} $UNDEFINED_VARIABLE' |
  envsubst "$(perl -e 'print "\$$_" for grep /^[_a-zA-Z]\w*$/, keys %ENV')"

Beware both disclose your environment variable names in the output of ps.

Instead, you could also do the whole thing in perl:

perl -pe 's{(?|\$\{([_a-zA-Z]\w*)\}|\$([_a-zA-Z]\w*))}{$ENV{$1}//$&}ge'

Beware it has the same limitations as envsubst in that it won't expand things like ${VAR:-x} and would expand $HOME in things like \$HOME or $$HOME which a shell wouldn't.

Tags:

Linux

Shell

Bash