77

Is there a way to prevent envsubst from substituting a $VARIABLE? For example, I would expect something like:

export THIS=THAT
echo "dont substitute \\\$THIS" | envsubst

and have it return

dont substitute $THIS

but instead I get

dont substitute \THAT

is there any escape character for doing this?

Sławomir Lenart
  • 7,543
  • 4
  • 45
  • 61
quinn
  • 5,508
  • 10
  • 34
  • 54

12 Answers12

85

If you give envsubst a list of variables, it only substitutes those variables, ignoring other substitutions. I'm not exactly sure how it works, but something like the following seems to do what you want:

$ export THIS=THAT FOO=BAR
$ echo 'dont substitute $THIS but do substitute $FOO' | envsubst '$FOO'
dont substitute $THIS but do substitute BAR

Note that $THIS is left alone, but $FOO is replaced by BAR.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 7
    That works but it's global. So you can't produce the "expected" output for `'The expansion of \$FOO is $FOO'`; either both FOOs are substituted or neither is. – rici Jul 25 '14 at 20:47
  • Hm, ok. I admit, I wasn't really thinking about what the use case for `envsubst` is. – chepner Jul 26 '14 at 16:28
  • 3
    No it doesn't do what the asker wanted. The above accepted answer is correct. – Khoi Feb 07 '17 at 07:31
  • 2
    @reza.safiyat See @rici's comment; `envsubst` doesn't respect escaped dollar signs in the string. – chepner Feb 07 '17 at 12:17
  • For more info in this and a few examples of doing this with nginx https://github.com/docker-library/docs/issues/496 – Danny Fenstermaker Feb 12 '18 at 17:49
56
export DOLLAR='$'

export THIS=THAT
echo '${DOLLAR}THIS' | envsubst

Or more clear:

export THIS=THAT
echo '${DOLLAR}THIS' | DOLLAR='$' envsubst
karolba
  • 956
  • 10
  • 19
  • 2
    This article quotes to claim that `DOLLAR='$'` is the worse way: https://qiita.com/takyam/items/e92e5a6ca1548cbd58db This answer should not be accepted. – mpyw Sep 27 '17 at 04:31
  • 7
    @mpyw: That page is in Japanese. Can you summarize its argument(s) against `DOLLAR='$'`, and indicate what it recommends instead? – ruakh Jan 23 '19 at 00:32
  • 1
    When talking about the `DOLLAR='$'` solution, the linked article's author states: "I do not think that this method is good, but I'm satisfied because I could accomplish what I wanted to do at first." However, later he quotes the man page for `envsubst` referencing the command line parameter "`SHELL-FORMAT`" in `envsubst [OPTION] [SHELL-FORMAT]`. This limits the variables: > "If a SHELL-FORMAT is given, only those environment variables that are referenced in SHELL-FORMAT are substituted; otherwise all environment variables references occurring in standard input are substituted." – TrinitronX Jun 26 '19 at 21:21
  • 4
    @TrinitronX Look at the `--variables` option behaviour. All `SHELL-FORMAT` gives you is a means to limit the set of variables that get substituted, not prevent a given instance of something from being substituted. – GufNZ Dec 18 '19 at 22:15
  • 1
    Yes, I'm aware ;-) Great to mention `--variables` flag too +1! The comment I posted was intended to translate, relay, and summarize the information from the blog post in Japanese, as well as the man page. I left out the bit about `--variables` for brevity. – TrinitronX Dec 20 '19 at 18:42
19

My workaround is as follows:

Original template:

$change_this
$dont_change_this

Editted template:

$change_this
§dont_change_this

Now you can process:

envsubst < $template | sed -e 's/§/$/g'

This relies on the character § not occurring anywhere else on your template. You can use any other character.

blueFast
  • 41,341
  • 63
  • 198
  • 344
18
$ echo $SHELL
/bin/bash
$ echo \$SHELL
$SHELL
$ echo \$SHELL | envsubst 
/bin/bash
$ echo \$\${q}SHELL | envsubst 
$SHELL

So doing $$ allows you to add a $ character. Then just "substitute" non-existent variable (here I used ${q} but can be something more meaningful like ${my_empty_variable} and you'll end up with what you need.

Just as with the paragraph solution - you need something special - here... a non-existent variable, which I like a bit more than performing additional sed on templates.

Lech Pawłaszek
  • 181
  • 1
  • 2
  • This is the best answer by far. It works even if you don't have the ability to introduce a special `DOLLAR=$` environment variable, and it doesn't require the use of a separate program like `sed`. If you only have control of the template those other solutions are total non-starters. – Paul Wheeler Jun 28 '22 at 02:46
10

If there's only one or two variables you don't want to expand, you can sort of whitelist them by temporarily setting them to their own name, like this:

$ echo 'one $two three $four' | four='$four' envsubst
one  three $four

Here, the $four variable gets replaced with $four, effectively leaving it unchanged.

Malvineous
  • 25,144
  • 16
  • 116
  • 151
8

In my case I wanted to only escape vars that aren't already defined. To do so run:

envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')"

aaron
  • 1,176
  • 10
  • 7
  • 3
    As a note: to use this in place e.g `envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < source.txt.template > target.txt` – WiR3D Oct 31 '19 at 19:53
1

Another way to "escape" some environment variable substitution is to use default value assignment (or any other variable processing) as envsubst will not substitute these:

$ export two=2
$ echo 'one $two three ${four:-}' | envsubst
one 2 three ${four:-}

The fourth envvar is not substituted, while in its output the processing to use defaulkt value is still there. This does not matter though, as processing this line later on will still deliver nothing if the variable is not set and its value when set.

BvdrHorst
  • 29
  • 1
  • 1
    `:-` is a shell thing, so this only works if you are running `envsubst` on a shell script, which seems to me pretty unlikely. It won't work (for example) in an `nginx.conf` – James Stevens Mar 09 '21 at 14:02
  • Indeed, if you need to inject some variables in a shell script, you are better off prefixing it with some variable assignments. E.g.: `{ printf -- '%s=%q\n' "$name1" "$value1" "$name2" "$value2" ... ; cat script-template.sh ; } > script.sh` – Vladimir Panteleev Jul 09 '23 at 15:40
1

Here's an alternative that I use, as it saves installing the entire gettext package for just one program. I have this awk script, I call envtmpl, it will swap any environment variable that looks like {{ENV-VAR}} for the value of ENV-VAR

#! /usr/bin/awk -f
{ for (a in ENVIRON) gsub("{{" _ a _ "}}",ENVIRON[a]); print }

So

$ echo "My shell '{{SHELL}}' is cool" | envtmpl
My shell '/bin/bash' is cool

As you can see, if {{ & }} aren't what you prefer, its really each to change and this script works fine with busybox's awk.

It's not going to be the world's fastest solution, but it's really easy to implement and I mostly run it to prepare config files, so speed is pretty irrelevant.

WARNING: The only major difference between this and envsubst is that this will NOT alter variables where no value exists. That is {{HAS-NO-VALUE}} will be left exactly as that, where as envsubst will remove those (replace them with blank).

You can fix this by adding more code into the awk, if you want.

James Stevens
  • 374
  • 2
  • 8
  • I've been asked about no-value items a few times, so I made a github repo!! https://github.com/james-stevens/envsub/blob/main/envsub <- this include a `gsub` to also remove the `{{XX}}` that have no value. The `gsub` works in GNU `awk`, but does nothing in Busybox `awk` – James Stevens Oct 15 '21 at 07:57
  • Careful, this does recursive substitution. Think about what will happen if a variable's *value* will have `{{NAME_OF_SOME_SECRET_VARIABLE}}`. – Vladimir Panteleev Jul 09 '23 at 15:53
  • It depends on the relatively arbitrary order of the names in the `ENVIRON` list whether you will get a recursive replacement or not. The variable `NAME_OF_SOME_SECRET_VARIABLE` will have to come *after* the name that holds it as a value, to get recursively substituted. Personally, I only use this for inserting environment variables into config file templates when starting up containers, so I have full control over the names & values - meaning recursion is not an issue for me – James Stevens Jul 10 '23 at 16:52
1

The way I did it is

export DONT_CHANGE_THIS=\${DONT_CHANGE_THIS}
envsubst < some-template.yml > changed.yml

So it will try to replace ${var} with \${var} and as output, you will get ${var} printed as it is

Akshay
  • 3,558
  • 4
  • 43
  • 77
0

I used escape character for this

MYENVVAR="\${MYENVVAR}"
export MYENVVAR
envsubst #whatever you want

then reset it to what actually I want

MYENVVAR="my value"
export MYENVVAR
kyo
  • 164
  • 14
0

I just connected parts of other answers to create one-liner that substitutes vars prefixed with $, but ignores $$:

echo "\$TEST ; \$\$l" > TEST_FILE cat TEST_FILE # $TEST ; $$l export TEST=1 cat TEST_FILE | sed -e 's/\$\$/§/g' | envsubst | sed -e 's/§/\$/g' # 1 ; $l
Emil
  • 629
  • 2
  • 7
  • 24
0

Expanding on the answer from @aaron, the substitution technique works when using Nginx-defined variables -- like $remote_addr -- alongside environment variables.

In my case, I'm running Nginx from Docker.

My directory structure:

docker-hello-world
├── certs/ 
│   ├── ssl_certificate_file.crt  # SSL Certificate named in .env
│   └── ssl_key_file.key  # SSL Key named in .env
├── nginx/
│   ├── .dockerignore  
│   ├── Dockerfile  
│   └── nginx.conf  # Nginx proxy server configuration
├── hello-world/
│   ├── .dockerignore 
│   ├── entrypoint.sh
│   ├── hello_world.py
│   └── Dockerfile
├── docker-compose.yml
├── environment.yml  # define the modules to include in the Python virtual environment
└── .env  # Application-wide environment variables

From my nginx/Dockerfile:

FROM nginx:latest
RUN apt-get update && rm -rf /var/lib/apt/lists/* && rm -rf /etc/nginx/user.conf.d/*
RUN touch /etc/nginx/nginx.conf; \
envsubst "$(env | sed -e 's/=.*//' -e 's/^/\$/g')" < /etc/nginx/templates/default.conf.template > /etc/nginx/nginx.conf;
Jesuisme
  • 1,805
  • 1
  • 31
  • 41