How To Create Templates 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
actual cat

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.