Stupid Templating With Shell, cat and envsubst
Published on 2020-07-14 20:26:00+02:00
Now something trivial and fun. Creating templates of documents or configurations in shell. There are two reasons why
I considered doing that instead of using some more verbose utilities. First off, availability - I have POSIX compliant
shell pretty much everywhere I go. It's a big part of my usual environment and because of that I'm used to it. That's
the second reason - frequency I use it with. It's good to mention that it's one of more verbose utilities out there.
Let's start with plain shell. I actually use this method to serve the blog posts via fcgiwrap. I use it with a single
file, but it can accept external files. However those files won't have their content expanded in any way. If you keep it
in a one script file then it's possible. Basically, I'm talking about cat
:
#!/bin/sh
cat /dev/fd/3 $@ /dev/fd/4 3<<BEFORE 4<<AFTER
<!doctype html>
<html lang="en">
BEFORE
<script src=""></script>
AFTER
What you see here is a combination of
heredoc and plain
redirection. It's done like this to avoid calling cat
more than once. The script simply concatenates BEFORE
heredoc with any arguments passed to the script, and finally with AFTER heredoc. The output is similar to what you see
in source of this page. The template is trimmed for the sake of the example.
Now, now. Heredoc can easily expand variables but if we were to cat before.inl $@ after.inl
it wouldn't
work. For that I use
envsubst
.
Consider file called template.conf:
server {
listen 80;
server_name $DOMAIN$ALIASES;
root /srv/http/$DOMAIN/public;
}
It could be wrapped in cat and heredoc, but for me it's not desired. Let's say I want my configuration templates to
be exactly that not executable scripts. There are three different solutions that I've heard: eval
,
sed
and envsubst
. Eval
can lead to dangerous situations and minor problems with
whitespace. Sed
is what I believe is the most typical and straight-forward solution. As for the last one,
it goes like this:
$ export DOMAIN=example.tld
$ export ALIASES=' www.example.tld'
$ envsubst '$DOMAIN$ALIASES' <template.conf
server {
listen 80;
server_name example.tld www.example.tld;
root /srv/http/example.tld/public;
}
First, you set the variables (if they are not yet there). Then call the envsubst
with a single variable
and redirect the template file into it. This single variable is called SHELL-FORMAT. It restricts the variables
that will be substituted in the input. It's completely optional, but if you have some $server_name
you
don't want to substitute, then it's kind of useful. It actually doesn't have any format, you just need to reference the
variables in it: '$DOMAIN$ALIASES'
, '$DOMAIN $ALIASES'
, and '$DOMAIN,$ALIASES'
all work the same.