m4 As A Simple Templating System
home // page // m4 As A Simple Templating System

m4 As A Simple Templating System

Sometimes, while working on a project, a simple templating system is necessary either for code or documentation generation. While you can get quite far with sed and awk, their nifty one-liners will eventually become monstrous, especially as your templates get more complex.

How do you deal with this growing complexity? For a lot of use-cases the simple m4 macro processor is exactly what you want. This command is probably installed on your machine without you even realizing it. It comes standard on Macs. On Linux it is part of the build-essential package and therefore probably installed on your distribution.

In this article we’ll briefly explore m4 and some of the interesting things it can do. If you’re looking for a dead simple templating system that is available on most Linux/BSD platforms then this might be just what you’re looking for.

m4 Basics

m4 is a macro processor, it takes simple text files, expands all of the macros defined in them, and produces a new text file. A simple m4 script that does nothing is the following:

If you run this through m4 you’ll see something like the following:

We can make this a bit more interesting my forcing m4 to evaluate something:

This will evaluate to something like the following:

Now if we want to define a variable and have it passed in via the command-line we can do this as follows:

We can then cause the variable NAME to be replaced by arbitrary text by specializing our invocation of m4:

This also allows us to pass in the output of evaluating other commands as inputs to templates, like the following:

You can also inline define static variables to avoid duplication as follows:

The output of this script is the following:

In this example we use the define macro to create a new static value, though it can also be used to define much more complex values as well. You’ll also notice the value “dnl” at the end of the line – this tells m4 to ignore the newline that immediately follows. If we removed the “dnl” we’d get the following output:

Notice the extra line before our output. This is because m4 does not replace the newlines after a “define” but instead simply echos them.

m4 For Templates

For most of the simplest templates, the above examples will likely be enough. That being said, there are times when you want a little more functionality from your templates. In what follows we’ll explain a few of the additional features of m4 using the following example:

If you run this through m4 you’ll get the following output containing a fragment of Rudyard Kipling’s Cold Iron:

So there are a few things happening here that will be interesting to unpack.

Diversions

The first thing you might notice is the presence of the divert macro, this macro essentially does two things:

  • When given a positive integer, it places all output generated by the following lines into the output buffer with that number.
  • When given a negative integer, it throws away any output that would be generated by the lines that follow it. Until a diversion to a positive integer buffer is encountered.

When m4 reaches the bottom of your file, it will output all text diverted to positive number buffers in numerical buffer number order. To really get a sense for what this means run the following example:

Here we first divert to a negative number buffer, effectively getting rid of all the output that might result from spurious newlines and the define statement. We then divert to buffer number 1 the text we actually want  in the middle section of the output. We then divert to buffer number 2 the text that we want at the end of the output. Finally, we divert to buffer number 0 the text that we want at the beginning of the output. Once m4 reaches the bottom it then outputs the contents of these buffers starting with buffer 0, then buffer 1, then buffer2.

This results in the following output:

As you can see, we can specify the output in any order that makes sense in our m4 file and guarantee that the output happens in the order that we expect. Removing the “divert(-1)” is also instructive, when removed from the above script you get the following output:

Notice the extra newline echoed following our “define” statement.

From this you should be able to see that the divert method is a nice way to do the following:

  1. Avoid spurious output from macro evaluations.
  2. Control the order in which the final output is displayed.

System Commands

The second major feature of the above script that you’ll notice is that we can ask m4 to grab the output of a system command and assign it to a variable, namely via the following line:

In this line we use the esyscmd (expand to output of system command) macro to capture the output of the date command. We then use the translit macro to remove the newline character that typically follows the output of date so that we don’t get unexpected newlines in our resulting output.

This could also be used to run a custom script and insert its output into a template. For example, let’s say we had the following ksh script in a file “test.sh”:

We could then insert the output of this script into our m4 template as follows:

Running this would yield the following output:

Conclusion

From the above I hope you’ve seen that m4 is a simple, powerful, and widely available tool for creating templates. I also think it is wildly underutilized given how simple and ubiquitous it is.

If you’re interested in learning more I highly recommend checking out the GNU m4 Manual.