Description
M4 is a hidden gem from the UNIX world and unfortunately often overlooked because the
documentation is VERY sparse. I hope the following pages will open the world of M4 to you
and making available to you one of the most powerful command line text generators. M4 is a
very sophisticated text generator which allows you to generate any kind of structured
text: You can create C source, C++ source, HTML scripts, serial letters, plain text files
etc.
You can enter M4 instructions from the command line but usually you will create a M4 script file first and then call M4 with the name(s) of all the script files. You could create a library of several set of you standard M4 function and then just pull them in on demand.
M4 will use the TEMP environment variable for its temporary files (m4*), if TEMP is not set M4 will place the temporary files into \temp, \tmp or \ directory - whichever is first available.
To avoid one more problem source M4 is stand alone and does not need the UnixDos DLL udbase.dll
First
we will present a few examples to give you a first idea of M4.
Then we will show how you can use M4 to generate CGI scripts (M4CGI).
And finally we will explain each M4 command or macro in detail.
Example1:
Here
is s small script defining a macro mymacro which just writes a fixed text and
inserts a name (see ex1.m4):
define(mymacro,Now getting name: $1)
mymacro
mymacro(Name)
mymacro(The Name)
mymacro(First,Middle,Last)
To
run it enter:
m4 ex1.m4
The result is:
Now getting name:
Now getting name: Name
Now getting name: The Name
Now getting name: First
You can see how M4 inserted at the $1 placeholder the first argument
supplied when the mymacro is called.
The extra arguments on the last call mymacro(First,Middle,Last) are ignored.
Example2:
In the following script we introduce the argument counter $#. It has to be
enclosed in the left and right single quotes so that it is not used as a literal text.
I also introduce the use of an internal function substr(text,start,len) which
extracts a sub-string text (the second name) starting at start (0
= first column) and extracting up to len characters. In effect this script
will print the supplied names with the only the initial extracted from the middle name.
define(mymacro,Now I got `$#' names: $1 `substr($2,0,1)' $3)
mymacro
mymacro(Name)
mymacro(The Name)
mymacro(First,Middle,Last)
mymacro(John,Fitzgerald,Kennedy)
To run it enter:
m4 ex2.m4
The result is:
Now I got 0 names:
Now I got 1 names: Name
Now I got 1 names: The Name
Now I got 3 names: First M Last
Now I got 3 names: John F Kennedy
Example3:
Now we will separate the previous script into the following two sections:
1. the macro definition section or library (file ex3_lib.m4)
define(mymacro,Now I got `$#' names: $1 `substr($2,0,1)' $3)
2. the application file (ex3_use.m4):
mymacro
mymacro(Name)
mymacro(The Name)
mymacro(First,Middle,Last)
mymacro(John,Fitzgerald,Kennedy)
To run it enter:
m4 ex3_lib.m4 ex3_use.m4
The result is again:
Now I got 0 names:
Now I got 1 names: Name
Now I got 1 names: The Name
Now I got 3 names: First M Last
Now I got 3 names: John F Kennedy
If you reverse the order M4 will not interpret the mymacro calls since they
are not yet defined:
m4 ex3_use.m4 ex3_lib.m4
The result is:
mymacro
mymacro(Name)
mymacro(The Name)
mymacro(First,Middle,Last)
mymacro(John,Fitzgerald,Kennedy)
Example4:
Now we will re-use the name macro calls in ex3_use.m4 and introduce another
internal macro:
ifelse(expr,val,true,false).
expr expression to test
val value is the value to compare the result against
true result to return if expr = val
false result to return if expr != val
This demonstrates that to can NEST the macros. In this example the getmiddle macro is either returning nothing if the middle name does not exist or extracts the first character and appends a dot. This getmiddle macro is the called from out original mymacro (see ex4_lib.m4):
define(getmiddle,`ifelse(len($1),0,,substr($1,0,1).)')
define(mymacro,Now I got `$#' names: $1 `getmiddle($2)' $3)
To run it enter:
m4 ex4_lib.m4 ex3_use.m4
The result is now:
Now I got 0 names:
Now I got 1 names: Name
Now I got 1 names: The Name
Now I got 3 names: First M. Last
Now I got 3 names: John F. Kennedy
Example5 (Serial Letter):
The following file (see let_lib.m4) contains the framework for your serial
letter:
define(date,Aug 12th 1990)
define(letter,
$1
$2
$3
$4
date()
Dear $5`,'
Please find enclosed our new product line presentation.
(your text...)
Sincerely`,'
Marketing Director
(NEWPAGE)
)
The following file (see let_use.m4) makes use of the frame work and creates
two example serial letters:
letter(XYZ Inc.,Attn: Mr. Green,PO BOX 12345,CITY CA 90010,John)
letter(ABC Inc.,Attn: Mr. Blue,PO BOX 7890,CITY NY 10010,Jack)
To generate the letter text enter:
m4 let_lib.m4 let_use.m4
Here is the output:
XYZ Inc.
Attn: Mr. Green
PO BOX 12345
CITY CA 90010
Aug 12th 1990
Dear John,
Please find enclosed our new product line presentation.
(your text...)
Sincerely,
Marketing Director
(NEWPAGE)
ABC Inc.
Attn: Mr. Blue
PO BOX 7890
CITY NY 10010
Aug 12th 1990
Dear Jack,
Please find enclosed our new product line presentation.
(your text...)
Sincerely,
Marketing Director
(NEWPAGE)
M4 CGI Mode (M4CGI)
If you are using M4 to generate HTML scripts within the CGI environment you are not
executing M4 from the command line, which can pose some problems.
To help you use M4 also in that environment we have implemented the following additional
features:
· no DLL needed - M4CGI/M4 is stand alone
· to merge the error output(stderr) with the regular output(stdout)
· Automatically change to the same directory where M4CGI.EXE resides,
so that you can use filenames without having to type the absolute pathnames
· switch trace on BEFORE the system wide definitions are loaded,
so you can trace even those or not having to add the traceon into any M4
script
· execute a default startup script for system wide definitions
To activate the CGI mode use the M4CGI.exe/M4CGIT.exe instead of M4.exe.
:
M4CGI: Merging error output:
Normally M4 will write error to stderr:
m4 bad.m4 > out
Will show:
m4: stdin(line 1) cannot open file 'bad.m4' (No such file or directory)
But M4CGI will redirect the error to the file and nothing is written to stderr:
m4cgi bad.m4 > out
M4CGI: Automatic change of default directory:
Normally M4 will stay in the current directory where you started M4:
m4 test.m4
This example will open test.m4 in the current directory.
But when you run M4 and you dont know in which directory you start from you would
have to specify the full path:
cd \anydir
m4 \unixdos\test.m4
To avoid this M4CGI/M4CGIT will first change to the same directory where the Executable
is and THEN run M4 (we assume M4CGI.exe is in c:\unixdos\M4CGI.exe):
m4 test.m4 (will run ./test.m4)
m4cgi test.m4 (will run c:/unixdos/test.m4)
So in other words: just place all the M4 scripts into the same directory where M4CGI/M4CGIT lives and just you can call these scripts without any absolute path reference
M4CGI: Automatic startup options:
In many cases you have a standard set of macros you use thoughout your other scripts with
either systemwide definitions or basic macros. You can obviously just specify this M4
script(s) with your standard functions on the command line:
m4 std.m4 script1.m4
m4 std.m4 script2.m4
But this could be a problem when you dont get a chance to specify command line
options in the CGI environment. So we implemented the automatic load feature:
When M4CGI start it will go to the same directory where M4 started and look for a file
named m4cgi.arg. If it is found it will use its contents as arguments AS IF
you would have entered then on the command line (we assume M4CGI.exe is in
c:\unixdos\M4CGI.exe):
c:\unixdos\m4cgi.arg contains:
std.m4
c:\unixdos\std.m4 contains:
define(macIPADDR,121.122.123.124.21)
c:\unixdos\href.m4 contains:
define(macHREF,<A HREF=macIPADDR>$1</A>)
HREF(My first hyperlink)
HREF(My 2nd hyperlink)
From the command line you would enter:
cd \anydir
m4cgi std.m4 href.m4
From the command line you enter:
m4cgi href.m4
And you should see:
<A HREF="121.122.123.124.21">My first hyperlink</A>
<A HREF="121.122.123.124.21">My 2nd hyperlink</A>
Here is what happened:
1. M4CGI checked if c:\unixdos\m4cgi.arg exists
2. if yes: read each line and interpret as a separate argument (std.m4)
3. when all lines are read and processed use the arguments you actually entered on the
command line and process them (href.m4)
So effectively you expanded M4s default behaviour always loading std.m4(or other script or scripts) BEFORE any files are processed from the command line. This could come in very handy when you just associate .m4 extensions with c:\unixdos\m4cgi.exe and then have no control what happens when Windows95 is calling M4CGI.
M4CGI: Automatic trace:
Normally M4/M4CGI will switch the debug trace on only when you place the
traceon macro into an M4 script or use the -t option. This can
become a problem if you are debugging code which is in the startup scripts (see above). So
to switch trace on BEFORE the internal startup script just use M4CGIT/M4T:
test.m4 contains:
define(mac1,just text $1)
mac1(here)
traceoff
the end
The following command:
m4cgi test.m4
will show:
just text here
the end
But now we switch trace on BEFORE the startup:
m4cgit test.m4
will show:
Trace(0): define(mac1,just text $1)
Trace(0): mac1(here)
just text here
Trace(0): traceoff
the end
(For a detailed description see the Online Help)
Command Line options:
m4 [-o{f}] [-t] [-Bn] [-Dn=v] [-Hn] [-Sn] [-s] [-Tn] [-Un] [fn1 [fn2]...]
m4cgi [-o{f}] [-t] [-Bn] [-Dn=v] [-Hn] [-Sn] [-s] [-Tn] [-Un] [fn1 [fn2]...]
fn1.. = input filename(s) (if none specified 'm4' will use STDIN)
-o{f} = OUTPUT : send all output to file 'f' instead of STDOUT
-t = TRACE : switch trace by default (for debugging)
(default is OFF unless you use 'traceon' macro
-Bn = BLOCKSIZE: specify the block size (default = 4096)
-Dn=v = DEFINE : define symbol 'n' to value 'v'
-Hn = HASH : set hash size to 'n' (default = 199)
-Sn = STACK : set stacksize to 'n' (default = 100)
-s = SOURCE : genrate '#line' directives to sync line to input
-Tn = TOKEN : set tokensize to 'n' (default = 512)
-Un = UNDEFINE : undefine symbol 'n'
(If no files are specified M4 will use STDIN)