1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
|
#[1]Linux Today » Feed
* [2]News
* [3]IT Management
* [4]Infrastructure
* [5]Developer
* [6]Security
* [7]High Performance
* [8]Storage
* [9]Blog
Search ____________________
[10]LinuxToday LinuxToday
---
[11]LinuxToday LinuxToday
* [12]News
* [13]IT Management
* [14]Infrastructure
* [15]Developer
* [16]Security
* [17]High Performance
* [18]Storage
* [19]Blog
____________________ (BUTTON) Search
[20]Home [21]Blog
Macro Magic: M4 Complete Guide
By Jerry Peek
May 9, 2019
A macro processor scans input text for defined symbols -- the macros --
and replaces that text by other text, or possibly by other symbols. For
instance, a macro processor can convert one language into another.
If you're a C programmer, you know cpp, the C preprocessor, a simple
macro processor. m4 is a powerful macro processor that's been part of
Unix for some 30 years, but it's almost unknown -- except for special
purposes, such as generating the sendmail.cf file. It's worth knowing
because you can do things with m4 that are hard to do any other way.
The GNU version of m4 has some extensions from the original V7 version.
(You'll see some of them.) As of this writing, the latest GNU version
was 1.4.2, released in August 2004. Version 2.0 is under development.
While you won't become an m4 wizard in three pages (or in six, as the
discussion of m4continues next month), but you can master the basics.
So, let's dig in.
Simple Macro Processing
A simple way to do macro substitution is with tools
like sed and cpp. For instance, the command sed's/XPRESIDENTX/President
Bush/' reads lines of text, changing every occurrence
of XPRESIDENTX to President Bush. sed can also test and branch, for
some rudimentary decision-making.
As another example, here's a C program with a cpp macro
named ABSDIFF() that accepts two arguments, a and b.
#define ABSDIFF(a, b)
((a)>(b) ? (a)-(b) : (b)-(a))
Given that definition, cpp will replace the code...
diff = ABSDIFF(v1, v2);
... with
diff = ((v1)>(v2) ? (v1)-(v2) : (v2)-(v1));
v1 replaces a everywhere, and v2 replace b. ABSDIFF() saves typing --
and the chance for error.
Introducing m4
Unlike sed and other languages, m4 is designed specifically for macro
processing. m4manipulates files, performs arithmetic, has functions for
handling strings, and can do much more.
m4 copies its input (from files or standard input) to standard
output. It checks each token (a name, a quoted string, or any single
character that's not a part of either a name or a string) to see if
it's the name of a macro. If so, the token is replaced by the macro's
value, and then that text is pushed back onto the input to be
rescanned. (If you're new to m4, this repeated scanning may surprise
you, but it's one key to m4 s power.) Quoting text, like ` text`,
prevents expansion. (See the section on "Quoting.")
m4 comes with a number of predefined macros, or you can write your own
macros by calling the define() function. A macro can have multiple
arguments- up to 9 in original m4, and an unlimited number in GNU m4.
Macro arguments are substituted before the resulting text is rescanned.
Here's a simple example (saved in a file named foo.m4):
one
define(`one', `ONE')dnl
one
define(`ONE', `two')dnl
one ONE oneONE
`one'
The file defines two macros named one and ONE. It also has four
lines of text. If you feed the file to m4 using m4 foo.m4, m4 produces:
one
ONE
two two oneONE
one
Here's what's happening:
*Line 1 of the input, which is simply the characters one and a newline,
doesn't match any macro (so far), so it's copied to the output as-is.
*Line 2 defines a macro named one(). (The opening parenthesis before
the arguments must come just after define with no whitespace between.)
From this point on, any input string one will be replaced with ONE.
(The dnl is explained below.)
*Line 3, which is again the characters one and a newline, is affected
by the just-defined macro one(). So, the text one is converted to the
text ONE and a newline.
*Line 4 defines a new macro named ONE(). Macro names are
case-sensitive.
*Line 5 has three space-separated tokens. The first two
are one and ONE. The first is converted to ONE by the macro
named one(), then both are converted to two by the macro named ONE().
Rescanning doesn't find any additional matches (there's no macro
named two()), so the first two words are output as two two. The rest of
line 5 (a space, oneONE, and a newline) doesn't match a macro so it's
output as-is. In other words, a macro name is only recognized when it's
surrounded by non-alphanumerics.
*Line 6 contains the text one inside a pair of quotes, then a newline.
(As you've seen, the opening quote is a backquote or grave accent; the
closing quote is a single quote or acute accent.) Quoted text doesn't
match any macros, so it's output as-is: one. Next comes the final
newline.
Input text is copied to the output as-is and that includes newlines.
The built-in dnlfunction, which stands for "delete to new line," reads
and discards all characters up to and including the next newline. (One
of its uses is to put comments into an m4 file.) Without dnl, the
newline after each of our calls to define would be output as-is. We
could demonstrate that by editing foo.m4 to remove the two dnl s. But,
to stretch things a bit, let's use sed to remove those two calls from
the file and pipe the result to m4:
$ sed `s/dnl//' foo.m4 | m4
one
ONE
two two oneONE
one
If you compare this example to the previous one, you'll see that there
are two extra newlines at the places where dnl used to be.
Let's summarize. You've seen that input is read from the first
character to the last. Macros affect input text only after they're
defined. Input tokens are compared to macro names and, if they match,
replaced by the macro's value. Any input modified by a macro is pushed
back onto the input and is rescanned for possible modification. Other
text (that isn't modified by a macro) is passed to the output as-is.
Quoting
Any text surrounded by `' (a grave accent and an acute accent) isn't
expanded immediately. Whenever m4 evaluates something, it strips off
one level of quotes. When you define a macro, you'll often want to
quote the arguments -- but not always. Listing One has a demo. It
uses m4 interactively, typing text to its standard input.
Listing One: Quoting demonstration
$ m4
define(A, 100)dnl
define(B, A)dnl
define(C, `A')dnl
dumpdef(`A', `B', `C')dnl
A: 100
B: 100
C: A
dumpdef(A, B, C)dnl
stdin:5: m4: Undefined name 100
stdin:5: m4: Undefined name 100
stdin:5: m4: Undefined name 100
A B C
100 100 100
CTRL-D
$
The listing starts by defining three macros A, B, and C. A has the
value 100. So does B: because its argument A isn't
quoted, m4 replaces A with 100 before assigning that value to B. While
defining C, though, quoting the argument means that its value becomes
literal A.
You can see the values of macros by calling the built-in
function dumpdef with the names of the macros. As
expected, A and B have the value 100, but C has A.
In the second call to dumpdef, the names are not quoted, so each name
is expanded to 100before dumpdef sees them. That explains the error
messages, because there's no macro named 100. In the same way, if we
simply enter the macro names, the three tokens are scanned repeatedly,
and they all end up as 100.
You can change the quoting characters at any time by
calling changequote. For instance, in text containing lots of quote
marks, you could call changequote({,})dnl to change the quoting
characters to curly braces. To restore the defaults, simply
call changequote with no arguments.
In general, for safety, it's a good idea to quote all input text that
isn't a macro call. This avoids m4 interpreting a literal word as a
call to a macro. Another way to avoid this problem is by using the
GNU m4 option --prefix-builtins or -P. It changes all built-in macro
names to be prefixed by m4_. (The option doesn't affect user-defined
macros.) So, under this option, you'd
write m4_dnl and m4_define instead of dnl and define, respectively.
Keep quoting and rescanning in mind as you use m4. Not to be tedious,
but remember that m4 does rescan its input. For some in-depth tips, see
"Web Paging: Tips and Hints on m4Quoting" by R.K. Owen, Ph.D.,
at [22]http://owen.sj.ca.us/rkowen/howto/webpaging/m4tipsquote.html.
Decisions and Math
m4 can do arithmetic with its built-in functions eval, incr,
and decr. m4 doesn't support loops directly, but you can combine
recursion and the decision macro ifelse to write loops.
Let's start with an example adapted from the
file /usr/share/doc/m4/examples/debug.m4(on a Debian system). It
defines the macro countdown(). Evaluating the macro with an argument of
5 -- as in countdown(5) -- outputs the text 5, 4, 3, 2, 1, 0, Liftoff!.
$ cat countdown.m4
define(`countdown', `$1, ifelse(eval($1 > 0),
1, `countdown(decr($1))', `Liftoff!')')dnl
countdown(5)
$ m4 countdown.m4
5, 4, 3, 2, 1, 0, Liftoff!
The countdown() macro has a single argument. It's broken across two
lines.That's fine in m4 because macro arguments are delimited by
parentheses which don't have to be on the same line. Here's the
argument without its surrounding quotes:
$1, ifelse(eval($1 > 0), 1,
`countdown(decr($1))', `Liftoff!')
)
$1 expands to the macro's first argument. When m4 evaluates that
countdown macro with an argument of 5, the result is:
5, ifelse(eval(5 > 0), 1,
`countdown(decr(5))', `Liftoff!')
The leading " 5, " is plain text that's output as-is as the first
number in the countdown. The rest of the argument is a call
to ifelse. Ifelse compares its first two arguments. If they're equal,
the third argument is evaluated; otherwise, the (optional) fourth
argument is evaluated.
Here, the first argument to ifelse, eval(5> 0), evaluates as 1
(logical" true") if the test is true (if 5 is greater than 0). So the
first two arguments are equal, and m4 evaluates countdown(decr(5)).
This starts the recursion by calling countdown(4).
Once we reach the base condition of countdown(0), the test eval(0>
0) fails and the ifelsecall evaluates `Liftoff!'. (If recursion is new
to you, you can read about it in books on computer science and
programming techniques.)
Note that, with more than four arguments, ifelse can work like
a case or switch in other languages. For instance,
in ifelse(a,b,c,d,e,f,g), if a matches b, then c; else
if d matches ethen f; else g.
The m4 info file shows more looping and decision techniques, including
a macro named forloop() that implements a nestable for-loop.
This section showed some basic math operations. (The info file shows
more.) You've seen that you can quote a single macro argument that
contains a completely separate string (in this case, a string that
prints a number, then runs ifelse to do some more work). This one-line
example (broken onto two lines here) is a good hint of m4' s power.
It's a mimimalist language, for sure, and you'd be right to complain
about its tricky evaluation in a global environment, leaving lots of
room for trouble if you aren't careful. But you might find this
expressive little language to be challenging enough that it's
addictive.
Building Web Pages
Let's wrap up this m4 introduction with a typical use: feeding an input
file to a set of macros to generate an output file. Here, the macro
file html.m4 defines three macros: _startpage(), _ul(), and _endpage().
(The names start with underscore characters to help prevent false
matches with non-macro text. For instance, _ul() won't match the HTML
tag <ul& gt;.) The _startpage() macro accepts one argument: the page
title, which is also copied into a level-1 heading that appears at the
start of the page. The _ul() macro makes an HTML unordered list. Its
arguments (an unlimited number) become the list items.
And _endpage() makes the closing HTML text, including a "last change"
date taken from the Linux date utility.
Listing Two shows the input file, and Listing Three is the HTML output.
The m4 macros that do all the work are shown in Listing Four. (Both the
input file and the macros are available online at
[23]http://www.linux-mag.com/downloads/2005-02/power.)
Listing Two: webpage.m4h, an" unexpanded" web page
_startpage(`Sample List')
_ul(`First item', `Second item',
`Third item, longer than the first two')
_endpage
Listing Three: An m4- generated web page
$ m4 html.m4 webpage.m4h > list.html
$ cat list.html
<html>
<head>
<title>Sample List</title>
</head>
<body>
<h1>Sample List</h1>
<ul>
<li>First item</li>
<li>Second item</li>
<li>Third item, longer than the first two</li>
</ul>
<p>Last change: Fri Jan 14 15:32:06 MST 2005
</p>
</body>
</html>
In Listing Four, both _startpage() and _endpage() are straightforward.
The esyscmdmacro is one of the many m4 macros we haven't covered -- it
runs a Linux command line, then uses the command's output as input
to m4. The _ul() macro outputs opening and closing HTML <ul> tags,
passing its arguments to the _listitems() macro via $@, which expands
into the quoted list of arguments.
_listitems() is similar to the countdown() macro shown
earlier: _listitems() makes a recursive loop. At the base condition
(the end of recursion), when $# (the number of arguments) is 0, the
empty third argument means that ifelse does nothing. Or, if there's one
argument ($# is 1), ifelse simply outputs the last list item inside a
pair of <li> tags. Otherwise, there's more than one argument, so the
macro starts by outputting the first argument inside <li> tags, then
calls _listitems() recursively to output the other list items. The
argument to the recursive call is shift($@). The m4 shift macro returns
its list of arguments without its first argument -- which, here, is all
of the arguments we haven't processed yet.
Notice the nested quoting: some of the arguments inside the (quoted)
definition of _listitems() are quoted themselves. This delays
interpretation until the macro is called. (m4 tracing, which we'll
cover next month, can help you see what's happening.)
Listing Four: html.m4, macros to generate an HTML page from Listing Two
define(`_startpage', `
<head>
<title>$1</title>
</head>
<body>
<h1>$1</h1>')dnl
dnl
define(`_endpage', `
<p>Last change: esyscmd(date)</p>
</body>
</html>')dnl
dnl
define(`_listitems', `ifelse($#, 0, ,
$#, 1, `<li>$1</li>',
`<li>$1</li>
_listitems(shift($@))')')dnl
define(`_ul', `<ul>
_listitems($@)
</ul>')dnl
This month, let's dig deeper into m4 and look at included
files, diversions, frozen files, and debugging and tracing. Along the
way, we'll see some of the rough edges of m4's minimalist language and
explore workarounds. Before we start, though, here's a warning from the
GNU m4 info page:
Some people[ find] m4 to be fairly addictive. They first use m4 for
simple problems, then take bigger and bigger challenges, learning how
to write complex m4 sets of macros along the way. Once really addicted,
users pursue writing of sophisticated m4 applications even to solve
simple problems, devoting more time debugging their m4 scripts than
doing real work. Beware that m4 may be dangerous for the health of
compulsive programmers.
So take a deep breath... Good. Now let's dig in again!
Included Files
m4's built-in include() macro takes m4's input from a named file until
the end of that file, when the previous input resumes. sinclude() works
like include() except that it won't complain if the included file
doesn't exist.
If an included file isn't in the current directory, GNU m4 searches the
directories specified with the -Icommand-line option, followed by any
directories in the colon-separated M4PATH environment variable.
Including files is often used to read in other m4 code, but can also be
used to read plain text files. However, if you're reading plain text
files, watch out for files that contain text that can confuse m4, such
as quotes, commas, and parentheses. One way to work around that problem
and read the contents of a random file is by using changequote() to
temporarily override the quoting characters and also
replacing include() with esyscmd(), which filters the file through a
Linux utility like tr or sed.
Listing One has a contrived example that shows one way to
read /etc/hosts, replacing parentheses with square brackets and commas
with dashes.
Listing One: A filtering version of the m4 include() macro
% cat readfile.m4
dnl readfile: display file named on
dnl command line in -Dfile=
dnl converting () to [] and , to -
dnl
file `file on 'esyscmd(`hostname')
changequote({,})dnl
esyscmd({tr '(),' '[]-' <} file)dnl
That's all.
changequote
% cat /etc/hosts
127.0.0.1 localhost
216.123.4.56 foo.bar.com foo
# Following lines are for `IPv6'
# (added automatically, we hope)
::1 ip6-localhost ip6-loopback
...
% m4 -Dfile=/etc/hosts readfile.m4
/etc/hosts file on foo
127.0.0.1 localhost
234.123.4.56 foo.bar.com foo
# Following lines are for `IPv6'
# [added automatically- we hope]
::1 ip6-localhost ip6-loopback
...
That's all.
The option -D or --define lets you define a macro from the command
line, before any input files are read. (Later, we'll see an cleaner way
to read text from arbitrary files with GNU m4's undivert().)
Diversions: An Overview
Normally, all output is written directly to m4's standard output. But
you can use the divert() macro to collect output into temporary storage
places. This is one of m4's handiest features.
The argument to divert() is typically a stream number, the ID of the
diversion that should get the output from now on.
*Diversion 0 is the default. Text written to diversion 0 goes to m4's
standard output. If you've been diverting text to another stream, you
can call divert(0) or just divert to resume normal output.
*Text written to diversions 1, 2, and so on is held until m4 exits or
until you call undivert(). (More about that in a moment.)
*Any text written to diversion -1 isn't emitted. Instead, diversion- 1
is "nowhere," like the Linux pseudo-file /dev/null. It's often used to
comment code and to define macros without using the pesky dnl macro at
the ends of lines.
*The divnum macro outputs the current diversion number.
Standard m4 supports diversions- 1 through 9, while GNU m4 can handle a
essentially unlimited number of diversions. The latter version of m4
holds diverted text in memory until it runs out of memory and then
moves the largest chunks of data to temporary files. (So, in theory,
the number of diversions in GNU m4 is limited to the number of
available file descriptors.)
All diversions 1, 2,..., are output at the end of processing in
ascending order of stream number. To output diverted text sooner,
simply call undivert() with the stream number. undivert() outputs text
from a diversion and then empties the diversion. So, immediately
calling undivert() again on the same diversion outputs nothing.
"Undiverted" text is output to the current diversion, which isn't
always the standard output! You can use this to move text from one
diversion to another. Output from a diversion is not rescanned for
macros.
Diverse Diversions
Before looking at the more-obvious uses of numbered diversions, let's
look at a few surprising ones.
As was mentioned, diversion- 1 discards output. One of the most
irritating types of m4 output is the newline chanacters after macro
definitions. You can stop them by calling dnl after each define, but
you can also stop them by defining macros after a call to divert(-1).
Here are two examples. This first example, nl, doesn't suppress the
newline from define...
`The result is:'
define(`name', `value')
name
... but the next example, nonl, does, by defining the macro inside a
diversion:
`The result is:'
divert(-1)
define(`name', `value')
divert(0)dnl
name
Let's compare the nl and nonl versions.
$ m4 nl
The result is:
value
$ m4 nonl
The result is:
value
The second divert() ends with dnl, which eats the the following
newline. Adding the argument (0), which is actually the default, lets
you write dnl without a space before it (which would otherwise be
output). You can use divert`'dnl instead, because an empty quoted
string (`') is another way to separate the divert and dnl macro calls.
Of course, that trick is more reasonably done around a group of
several define s. You can also write comments inside the same kind of
diversion. This is an easy way to write blocks of comments without
putting dnl at the start of each line. Just remember that
macros are recognized inside the diversion (even though they don't make
output). So, the following code increments i twice:
divert(-1)
Now we run define(`i', incr(i)):
define(`i', incr(i))
divert`'dnl
dnl can start comments, and that works on even the oldest versions of
m4. Generally, # is also a comment character. If you put it at the
start of the comment above, as in #Now..., then i won't be incremented.
Before seeing the "obvious" uses of diversions, here's one last item
from the bag of diversion tricks. GNU m4 lets you output a file's
contents by calling undivert() instead of include(). The advantage is
that, like undiverting a diversion, "undiverting" a file doesn't scan
the file's contents for macros. This lets you avoid the really ugly
workaround showed in Listing One. With GNU m4, you could have written
simply:
undivert(`/etc/hosts')
Diversions as Diversions
The previous section showed some offbeat uses of divert(). Now let's
see a more obvious use: splitting output into parts and reassembling
those parts in a different order.
Listing Two, Three, and Four show a HTML generator that outputs the
text of each top-level heading in two places: in a table of contents at
the start of the web page, and again, later, in the body of the web
page. The table of contents includes links to the actual headings later
in the document, which will have an anchor (an HTML id).
Listing Two has the file, htmltext.m4, with the macro calls. Listing
Three shows the HTML output from the macros (which omits the blank
lines, because HTML parsers ignore them). Listing Four shows the
macros, which call include() to bring in the htmltext.m4 file at the
proper place. (Blank lines have been added to the macros to make the
start and end of each macro more obvious.)
Listing Two: m4 macro calls: the htmltext.m4 file
_h1(`First heading')
_p(`The first paragraph.')
_h1(`Second heading')
_p(`The second paragraph.
Yadda yadda yadda')
_h1(`Third heading')
_p(`The third paragraph.')
Listing Three: HTML output from the htmltext.m4 file
<strong>Table of contents:</strong>
<ol>
<li><a href="#H1_1">First heading</a></li>
<li><a href="#H1_2">Second heading</a></li>
<li><a href="#H1_3">Third heading</a></li>
</ol>
<h1 id="H1_1">First heading</h1>
<p>
The first paragraph.
</p>
<h1 id="H1_2">Second heading</h1>
<p>
The second paragraph.
Yadda yadda yadda
</p>
<h1 id="H1_3">Third heading</h1>
<p>
The third paragraph.
</p>
Listing Four: The m4 code that makes the HTML in Listing Three
define(`_h1count', 0)
define(`_h1', `divert(9)
define(`_h1count', incr(_h1count))
<li><a href="`#'H1`_'_h1count">$1</a></li>
divert(1)
<h1 id="H1`_'_h1count">$1</h1>
divert')
define(`_p', `divert(1)
<p>
$1
</p>
divert')
include(`htmltext.m4')
<strong>Table of contents:</strong>
<ol>
undivert(9)
</ol>
undivert(1)
Let's look at the code in Listing Four.
*The _h1count macro sets the number used at the end of each HTML id.
It's incremented by a define call inside the _h1 macro.
*The _h1 (heading level 1) macro starts by calling divert(9). The code
used diversion 9 to store the HTML for the table of contents. After
incrementing _h1count, the macro outputs a list item surrounded
by <li> and </li>tags. (The <ol> tags come later: when the code
undiverts diversion 9.) Notice that the # is quoted to keep it from
being treated as an m4 comment character. In the same way, the
underscore is quoted (`_'), since it's used as part of the
HTML id string (for instance, href="#H1_2'').
A final call to divert switches output back to the normal diversion 0,
which is m4's standard output.
*The _p (paragraph) macro is straightforward. It stores a pair
of <p> tags with the first macro argument in-between in diversion 1.
*A call to include() brings in the file htmltext.m4 (Listing Two). This
could have done this in several other ways, on the m4 command line, for
instance.
*Finally, the call undivert(9) outputs the table of contents surrounded
by a pair of ordered-list tags, followed by the headers and paragraphs
from undivert(1).
This example shows one use of diversions: to output text in more than
one way. Another common use -- in sendmail, for instance -- is
gathering various text into "bunches" by its type or purpose.
Frozen Files
Large m4 applications can take time to load. GNU m4 supports a feature
called frozen files that speeds up loading of common base files. For
instance, if your common definitions are stored in a file
named common.m4,you can pre-process that file to create a frozen file
containing the m4 state information:
$ m4 -F common.m4f common.m4
Then, instead of using m4 common.m4, you use m4 -R common.m4f for
faster access to the commondefinitions.
Frozen files work in a majority of cases, but there are gotchas. Be
sure to read the m4 info file (type info m4) before you use this
feature.
Debugging and Tracing
m4's recursion and quoting can make debugging a challenge. A thorough
understanding of m4 helps, of course, and the techniques shown in the
next section are worth studying. Here are some built-in debugging
techniques:
*To see a macro definition, use dumpdef(), which was covered last
month. dumpdef() shows you what's left after the initial layer of
quoting is stripped off of a macro definition and any substitutions are
made.
*The traceon() macro traces the execution of the macros you name as
arguments, or, without a list of macros, it traces all macros. The
trace output shows the depth of expansion, which is typically 1, but
can be greater if a macro contains macro calls. Use traceoff to stop
tracing.
*The debugmode() macro gives you a lot of control over debugging
output. It accepts a string of flags, which are described in the m4
info file. You can also specify debugging flags on the command line
with -d or --debug. These flags also affect the output
of dumpdef() and traceon().
More about m4
Last month and this, you've seen some highlights of m4. If you have the
GNU version of m4, its info page (info m4) is a good place to learn
more.
R.K. Owen's quoting page
([24]http://owen.sj.ca.us/rkowen/howto/webpaging/m4tipsquote.html) has
lots of tips about -- what else -- quoting in m4. His site also has
other m4 information and examples.
Ken Turner's technical report "CSM-126: Exploiting the m4 Macro
Language," available
from [25]http://www.cs.stir.ac.uk/research/publications/techreps/previo
us.html, shows a number of m4 techniques.
*A Google search for m4 macro turns up a variety of references.
To find example code, try a search with an m4-specific macro name,
like m4 dnl and m4 divert -motorway. (In Google, the
-motorway avoids matches of the British road named the M4. You
can also add -sendmail to skip sendmail- specific information.)
*Mailing lists about m4 are at [26]http://savannah.gnu.org/mail/?group=
m4.
Happy m4 hacking!
Jerry Peek is a freelance writer and instructor who has used Unix and
Linux for over 20 years. He's happy to hear from readers;
see [27]http://www.jpeek.com/contact.html.
[28]Facebook
[29]
Twitter
[30]
Linkedin
[31]
Email
[32]
Print
Get the Free Newsletter!
Subscribe to Developer Insider for top news, trends, & analysis
Email Address ____________________
[ ] By subscribing, you agree to our [33]Terms of Use and [34]Privacy
Policy.
(BUTTON) Subscribe
Must Read
[35]Developer
[36]How to Install MariaDB on Ubuntu 24.04: A Step-By-Step Guide
[37]News
[38]Ubuntu 22.04.5 LTS (Jammy Jellyfish) Is Now Available Powered by Linux
Kernel 6.8
[39]News
[40]KDE Gear 24.08.1 Apps Collection Rolls Out, Here's What's New
[41]News
[42]Ardour 8.7 DAW Debuts With Enhanced Features and Bug Fixes
[43]Security
[44]New WordPress Malware Bypasses Top 14 Security Scanners
[45]LinuxToday LinuxToday
LinuxToday is a trusted, contributor-driven news resource supporting
all types of Linux users. Our thriving international community engages
with us through social media and frequent content contributions aimed
at solving problems ranging from personal computing to enterprise-level
IT operations. LinuxToday serves as a home for a community that
struggles to find comparable information elsewhere on the web.
[46]Facebook
[47]Linkedin
[48]Twitter
* [49]News
* [50]IT Management
* [51]Infrastructure
* [52]Developer
* [53]Security
* [54]High Performance
* [55]Storage
* [56]Blog
Our Brands
* [57]Privacy Policy
* [58]Terms
* [59]About
* [60]Contact
* [61]Advertise
* [62]California - Do Not Sell My Information
Advertiser Disclosure: Some of the products that appear on this site
are from companies from which TechnologyAdvice receives compensation.
This compensation may impact how and where products appear on this site
including, for example, the order in which they appear.
TechnologyAdvice does not include all companies or all types of
products available in the marketplace.
IFRAME: [63]https://www.googletagmanager.com/ns.html?id=GTM-T4P43PZ
×
References
Visible links:
1. https://www.linuxtoday.com/feed/
2. https://www.linuxtoday.com/news/
3. https://www.linuxtoday.com/it-management/
4. https://www.linuxtoday.com/infrastructure/
5. https://www.linuxtoday.com/developer/
6. https://www.linuxtoday.com/security/
7. https://www.linuxtoday.com/high-performance/
8. https://www.linuxtoday.com/storage/
9. https://www.linuxtoday.com/blog/
10. https://www.linuxtoday.com/
11. https://www.linuxtoday.com/
12. https://www.linuxtoday.com/news/
13. https://www.linuxtoday.com/it-management/
14. https://www.linuxtoday.com/infrastructure/
15. https://www.linuxtoday.com/developer/
16. https://www.linuxtoday.com/security/
17. https://www.linuxtoday.com/high-performance/
18. https://www.linuxtoday.com/storage/
19. https://www.linuxtoday.com/blog/
20. https://www.linuxtoday.com/
21. https://www.linuxtoday.com/blog/
22. http://owen.sj.ca.us/rkowen/howto/webpaging/m4tipsquote.html
23. http://www.linux-mag.com/downloads/2005-02/power
24. http://owen.sj.ca.us/rkowen/howto/webpaging/m4tipsquote.html
25. http://www.cs.stir.ac.uk/research/publications/techreps/previous.html
26. http://savannah.gnu.org/mail/?group
27. http://www.jpeek.com/contact.html
28. https://www.facebook.com/sharer.php?u=https://www.linuxtoday.com/blog/macro-m4-guide/
29. https://twitter.com/intent/tweet?text=Macro%20Magic:%20M4%20Complete%20Guide&url=https://www.linuxtoday.com/blog/macro-m4-guide/&via=Linux%20Today
30. https://www.linkedin.com/shareArticle?mini=true&url=https://www.linuxtoday.com/blog/macro-m4-guide//&title=Macro%20Magic:%20M4%20Complete%20Guide
31. mailto:?subject=Macro%20Magic:%20M4%20Complete%20Guide&body=https://www.linuxtoday.com/blog/macro-m4-guide/
32. https://www.linuxtoday.com/blog/macro-m4-guide/
33. https://technologyadvice.com/terms-conditions/
34. https://technologyadvice.com/privacy-policy/
35. https://www.linuxtoday.com/developer/
36. https://www.linuxtoday.com/developer/how-to-install-mariadb-on-ubuntu-24-04-a-step-by-step-guide/
37. https://www.linuxtoday.com/news/
38. https://www.linuxtoday.com/news/ubuntu-22-04-5-lts-jammy-jellyfish-is-now-available-powered-by-linux-kernel-6-8/
39. https://www.linuxtoday.com/news/
40. https://www.linuxtoday.com/news/kde-gear-24-08-1-apps-collection-rolls-out-heres-whats-new/
41. https://www.linuxtoday.com/news/
42. https://www.linuxtoday.com/news/ardour-8-7-daw-debuts-with-enhanced-features-and-bug-fixes/
43. https://www.linuxtoday.com/security/
44. https://www.linuxtoday.com/news/new-wordpress-malware-bypasses-top-14-security-scanners/
45. https://www.linuxtoday.com/
46. https://www.facebook.com/LinuxToday-635265507098561/
47. https://www.linkedin.com/company/linuxtoday/
48. https://twitter.com/linuxtoday?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor
49. https://www.linuxtoday.com/news/
50. https://www.linuxtoday.com/it-management/
51. https://www.linuxtoday.com/infrastructure/
52. https://www.linuxtoday.com/developer/
53. https://www.linuxtoday.com/security/
54. https://www.linuxtoday.com/high-performance/
55. https://www.linuxtoday.com/storage/
56. https://www.linuxtoday.com/blog/
57. https://www.linuxtoday.com/privacy-policy/
58. https://technologyadvice.com/terms-conditions/
59. https://technologyadvice.com/about-us/
60. https://technologyadvice.com/contact-us/
61. https://solutions.technologyadvice.com/digital-advertising-solutions/
62. https://technologyadvice.com/privacy-policy/ccpa-opt-out-form/
63. https://www.googletagmanager.com/ns.html?id=GTM-T4P43PZ
Hidden links:
65. https://www.linuxtoday.com/blog/macro-m4-guide/
66. https://www.linuxtoday.com/blog/macro-m4-guide/
67. https://technologyadvice.com/
68. https://www.eweek.com/
69. https://www.datamation.com/
70. https://project-management.com/
71. https://www.webopedia.com/
72. https://www.esecurityplanet.com/
73. https://www.serverwatch.com/
74. https://www.itbusinessedge.com/
75. https://www.linuxtoday.com/blog/macro-m4-guide/
|