The m4 Preprocessor
Creating a configuration file with m4(1) is simplicity itself. The m4(1) program is a macro preprocessor that produces a sendmail configuration file by processing a file of m4 commands. Files of m4 commands traditionally have names that end in the characters .m4 (the same as files used for building the sendmail binary). For building a configuration file, the convention is to name a file of m4 commands with an ending of .mc (for macro configuration). The m4 process reads that file and gathers definitions of macros, then replaces those macros with their values and outputs a sendmail configuration file.
With m4, macros are defined (given values) like this:
define(macro, value)
Here, the macro
is a symbolic name that
you will use later. Legal names must begin with an underscore or
letter and can contain letters, digits, and underscores. The
value
can be any arbitrary text. A comma
separates the two, and that comma can be followed by optional
whitespace.
There must be no space between the define
and the
left parenthesis. The definition ends with the right parenthesis.
To illustrate, consider this one-line m4 source file named /tmp/x:
input text to be converted ↓ define(A,B)A ↑ the m4 definition
When m4 is run to process this file, the output
produced shows that A
(the
input) is redefined to become
B
:
% m4 /tmp/x
B
m4 Is Greedy
The m4 program is greedy. That is, if a macro is already defined, its value will replace its name in the second declaration. Consider this input file:
define(A,B) define(A,C) A B
Here, the first line assigns the value B
to the
macro named A
. The second line notices that
A
is a defined macro, so m4
replaces that A
with B
and then
defines B
as having the value
C
. The output of this file, after processing with
m4, will be:
C C
To prevent this kind of greedy behavior (and to prevent the confusion it can create), you can quote an item to prevent m4 from interpreting it. You quote with m4 by surrounding each item with left and right single quotes:
define(A,B) define(`A',C) A B
Here, the first line defines A
as
B
like before. But the second line no longer sees
A
as a macro. Instead, the single quotes allow
A
to be redefined as C
. So the
output is now:
C B
Although it is not strictly necessary, we recommend that all macro and value pairs be quoted. The preceding line should generally be expressed like this:
define(`A',`B') define(`A',`C') A B
This is the form we use when illustrating m4 throughout this book, including the previous two chapters.
m4 and dnl
Another problem with m4 is that it replaces its
commands with empty lines. The earlier define
commands, for example, will actually print like this:
← a blank line ← a blank line C B
To suppress
this insertion of blank lines, you can use the special
m4 command dnl
(for Delete
through New Line). That command looks like this:
define(`A',`B')dnl
define(`A',`C')dnl
A B
You can use dnl
to remove blank lines where they
might prove inconvenient or unsightly in a configuration file.
The dnl
command can also be used to put comments
into an mc file. Just be sure to put a blank
line after the last dnl
because each
dnl
gobbles both the text and the newline:
dnl This is a comment. ←note the extra blank line
m4 and Arguments
When an m4
macro name is immediately followed by a right parenthesis, it is
treated like a function call. Arguments given to it in that role are
used to replace $
digit
expressions in the original definition. For example, suppose the
m4 macro CONCAT is defined like this:
define(`CONCAT',`$1$2$3')dnl
and then later used like this:
CONCAT(`host', `.', `domain')
The result will be that host
will replace
$1
, the dot will replace $2
,
and the domain
will replace $3
,
all jammed tightly together just as '$1$2$3'
were:
host.domain
Macro arguments are used to create such techniques as FEATURE( ) and OSTYPE( ), which are described later in this chapter.
The DOL m4 Macro
Ordinarily, the
$
character is interpreted by
m4 as a special character when found inside its
define
expressions:
define(`A', `$2') ↑ the $ makes $2 an m4 positional variable
There might be times, however, when you might want to put a literal
$
character into a definition—perhaps when
designing your own DOMAIN, FEATURE, or HACK files.
You place a literal $
into a definition with the
DOL m4 macro. For example:
define(`DOWN', `R DOL(*) < @ $1 > DOL(*) DOL(1) < @ $2 > DOL(2)')
Here, we define the m4 macro named DOWN, which
takes two arguments ($1
and
$2
). Notice how the $
character
has meaning to m4. This newly created DOWN macro
can then be used in one of your .m4
files, perhaps
like this:
DOWN(badhost, outhost)
DOWN creates a rule by substituting the argument
(badhost
for the $1
in
its definition, and the outhost
) for the
corresponding $2
. The substitution looks like
this:
R DOL(*) becomes -> R $* < @ $1 > becomes -> < @ badhost > DOL(*) becomes -> $* DOL(1) becomes -> $1 < @ $2 > becomes -> < @ outhost > DOL(2) becomes -> $2
After substitution, the following new rule is the result:
R $* < @badhost
> $* $1 < @outhost
> $2
The DOL m4 macro allowed the insertion of
$
characters (such as $*
) and
protects you from having the literal use of $
characters being wrongly interpreted by m4.
Needless to say, you should never redefine the DOL m4 macro.
Get Sendmail, 3rd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.