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.