Andrew
Andrew

Reputation: 6860

envsubst: command not found on Mac OS X 10.8

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

Answers (7)

iRaS
iRaS

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

Armel Chausse
Armel Chausse

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.

Unsafe raw Bash alternative to 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.

How does it work?

Let's break down the command:

  1. 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.
  2. This means that in our command, this whole string gets expanded by the shell before 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
  1. This command then gets executed by the shell, populating 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

mklement0
mklement0

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)
  • Commands directly built into 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 builtin
  • envsubst 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 references
  • whereas eval 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

mattmc3
mattmc3

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

cyberz
cyberz

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

ymonad
ymonad

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

cobberboy
cobberboy

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

Related Questions