Reputation: 6860
When I try to run a script that contains the envsubst command, I get this error. Looking online, this seems to be a standard bash command, so I am not sure what to install in order to get it to work.
Upvotes: 114
Views: 98541
Reputation: 2136
WARNING This answer is using eval and there is a reason they say eval is evil. The input you give this command can executed any command in your name.
I'm using this now in my bash script that requires envsubst:
if ! which envsubst > /dev/null 2>&1; then
envsubst() {
while read line; do
line=$( echo $line | sed 's/"/\\"/g' )
eval echo $line
done
}
fi
you can use it as the envsubst command - of course it's not feature complete or something else:
envsubst <<<'Honey, I am $HOME.'
envsubst < input > output 2> corrupt
Upvotes: 3
Reputation: 1
Fair warning: this doesn't answer the original question, but rather offers an unsafe alternative to envsubst
when you cannot / do not want to install it on a system.
envsubst
Instead of:
envsubst < input_file > output_file
You can do:
eval "cat <<EOF > output_file
$(cat input_file)
EOF"
You just need to make sure that all $
characters in input_file
that are followed by alphanumerical characters and are not meant to be expanded to env vars are escaped (\$
).
SECURITY WARNING: As indicated in another comment, using eval
in user facing scripts is extremely dangerous without the right precautions. Do not use this alternative if input_file
is user provided, as any command put into a $(...)
construct inside input_file
will be executed by the shell.
Let's break down the command:
eval
sends all of its arguments to the shell for execution, after the shell has already expanded them. For example export x=SHELL && eval "echo \$$x"
sends echo $SHELL
to the shell for execution, outputting /bin/bash
or whatever your SHELL
variable evaluates to.eval
even comes into play:"cat <<EOF > output_file
$(cat input_file)
EOF"
As $(cat input_file)
outputs the content of the file, for an input_file
containing My shell is $SHELL and I love \$\$
, the resulting string (and therefore the command being sent to the shell by eval
) would be:
cat <<EOF > output_file
My shell is $SHELL and I love \$\$
EOF
output_file
with the following content:My shell is /bin/bash and I love $$
For more details on how shell expansions work in Bash, see Bash manual - Shell Expansions.
For more details on eval
, see this Unix StackExchange answer.
Upvotes: -2
Reputation: 437803
tl;dr
macOS, as of v15 (Sequoia), does not ship with an envsubst
utilitity (it isn't a part of Bash, and it isn't a POSIX-mandated utility).
You can install it via Homebrew, as shown in cobberboy's answer.
Alternatively, define a shell function that emulates the behavior; see the next section for limitations and caveats:
# Usage: envsubst <sample.txt
envsubst() { eval "echo \"$(sed 's/"/\\"/g')\""; }
# Alternatively, for cross-platform use, you can define it
# *conditionally*, i.e. only if no envsubst utility is present.
which envsubst &>/dev/null || envsubst() { eval "echo \"$(sed 's/"/\\"/g')\""; }
To clear up potential confusion:
envsubst
is an external executable and thus not part of Bash; external executables are platform-dependent, both in terms of which ones are available as well as their specific behavior and the specific options they support (though, hopefully, there is a common subset based on the POSIX specifications)bash
are called builtins, and only they can be relied upon to be present on all platforms.To test whether a given command is a builtin, use:[1]
type <cmdName>
In the case at hand, running type envsubst
on macOS 15 (Sequoia) returns -bash: type: envsubst: not found
, from which you can infer:
envsubst
is NOT a builtinenvsubst
is not in your system's $PATH
(and thus likely not present on your system)(By contrast, running the same on command on, e.g., a Ubuntu 12.04 system returns envsubst is hashed (/usr/bin/envsubst)
, which tells you that the utility is present and where it is located.)
A makeshift alternative to envsubst
is to use eval
, although the usual caveat applies: use eval
only on strings whose content you control or trust:
Assume a sample.txt
file containing text with unexpanded variable references; e.g.:
cat > sample.txt <<'EOF'
Honey, I'm $USER
and I'm $HOME.
EOF
The equivalent of:
envsubst < sample.txt
is:
# See below for caveats and a function wrapper.
{ eval "echo \"$(sed 's/"/\\"/g')\""; } < sample.txt
There is a crucial difference, however:
envsubst
expands only environment variable referenceseval
will expand shell variable references too - as well as embedded command substitutions, which is what makes use of eval
a security concern.A function wrapper for the above (can easily be turned into a script):
envsubst() { eval "echo \"$(sed 's/"/\\"/g')\""; }
If you want to define the function conditionally, i.e. only if no envsubst
utility is present:
which envsubst &>/dev/null || envsubst() { eval "echo \"$(sed 's/"/\\"/g')\""; }
[1] If a given command is a builtin, a message such as <command> is a shell builtin
prints. To test this condition programmatically, use something like type <command> 2>/dev/null | grep -q builtin && echo "IS A BUILTIN"
(All major POSIX-compatible shells (bash
, zsh
, dash
, ksh
), even though the wording of the full message differs slightly, have the word "builtin" in their stdout message for builtins. Using type -t
to only output a single word describing the command is tempting, but only supported in bash
; if you use the latter, you could use [[ $(type -t <command>) == builtin ]] && echo "IS A BUILTIN"
.
Upvotes: 27
Reputation: 18335
If you don't want to bother installing homebrew and gettext, and can't make heads or tails of perl, a simple python script also does the trick:
envsubst() {
python -c 'import os,sys;[sys.stdout.write(os.path.expandvars(l)) for l in sys.stdin]'
}
The advantage of this over eval
solutions is that it only handles variable replacement and won't execute arbitrary scripts like eval
does.
$ cat > sample.txt <<'EOF'
Honey, I'm $USER
and I'm $HOME.
Danger: $( echo 'eval can do evil here, but python expandvars rocks' )
EOF
$ envsubst < sample.txt
Honey, I'm mattmc3
and I'm /Users/mattmc3.
Danger: $( echo 'eval can do evil here, but python expandvars rocks' )
Upvotes: 4
Reputation: 923
If you don't want to bother installing homebrew and gettext, a one line perl executable will do:
#!/usr/bin/perl -p
$_ =~ s/\Q${$1||$2}/$ENV{$1?$2:$4}/ while $_ =~ /(\$\{([^}]+)})|(\$(\w+))/g;
Upvotes: 4
Reputation: 12090
Edit: @cobberboy 's anwer is more correct. upvote him.
brew install gettext
brew link --force gettext
Following is my old answer:
envsubst
is included in gettext
package.
Therefore you may compile it by your own, using standard build tools such as make
or using homebrew
.
However, it seems to have little issue when installing gettext
in MacOS.
See following url for details: How to install gettext on MacOS X
Upvotes: 159
Reputation: 6205
brew install gettext
brew link --force gettext
This will enable envsubst on OS X, and force it to link properly. It requires homebrew to be installed.
Upvotes: 288