这篇笔记的主要内容取自 Michael Breen 所写的《 Notes on the M4 Macro Language 》
 M4 提供了两种条件宏,    ifdef 宏用于判断宏是否定义,    ifelse 宏是判断表达式的真假。  
ifdef(`a', b)    对于上述条件宏,如果    a 是已定义的宏,那么这条语句的展开结果是    b 。  
ifdef(`a', b, c)    对于上述条件宏,如果    a 是未定义的宏,这条语句的展开结果是    c 。  
被测试的宏,它的定义可以是空字串,例如:
define(`def') `def' is ifdef(`def', , not) defined.  # -> def is defined.        ifelse(a,b,c,d) 会比较字符串    a 与    b 是否相同,如果它们相同,这条语句的展开结果是字符串    c ,否则展开为字符串    d 。  
     ifelse 可以支持多个分支,例如:  
ifelse(a,b,c,d,e,f,g)   它等价于:
ifelse(a,b,c,ifelse(d,e,f,g))    M4 只认识文本,所以在它看来,数字也是文本。不过 M4 提供了内建宏    eval ,这个宏可以对整型数的运算表达式进行『求值』——求值结果在 M4 看来依然是文本。  
例如:
define(`n', 1)dnl `n' is ifelse(eval(n < 2), 1, less than, eval(n == 2), 1, , greater than) 2        eval(n < 2) 是对    n < 2 这个逻辑表达式进行『求值』,结果是字符串    1 ,因此    ifelse 的第一个参数与第二个参数相等,因此    ifelse 宏的展开结果是其第三个参数    less than ,所以展开结果为:  
n is less than 2    我觉得没必要用 M4 来计算,它的数字计算功能太孱弱,而且不支持浮点数。可以考虑用    GNU bc 来弥补它的不足。在 M4 中,可以通过    esyscmd 宏访问 Shell,例如:  
2.1 ifelse(eval(esyscmd(`echo "2.1 > 2.0" | bc')), 1, `greater than', `less than') 2.0   展开结果为:
2.1 greater than 2.0    不过,    esyscmd 是 GNU m4 对    syscmd 的扩展,别的 m4 的实现可能没有这个宏。  
 在遵循 POSIX 标准的 M4 中,一个宏最多可以有 9 个参数(好像 Shell 脚本里的函数也最多支持 9 个参数),可以用    $1, ..., $9 来引用它们。GNU 的 m4 不限制宏的参数数量。  
 宏的参数默认值是空字串。例如,如果向一个宏传递 2 个参数值,那么它的第 3 个参数值是空字串。如果不向宏传递任何参数,那么它的所有参数值都是空字串。也就是说,不管你向宏传还是不传参数,只要你用    $n 来引用某个参数,如果这个参数值的确存在,就可以取而用之,否则只能拿到空的字串。  
 在宏的定义中,参数的引用,例如    $1 总是立即被展开的,不管它外围有没有引号。如果不希望参数立即展开,可以用引号来维持,例如:  
define(`world', `Hello World')  define(`say_hello', `$1') say_hello(`world')           # -> Hello World!  define(`say_hello', `$`'1') say_hello(`world')           # -> $1    在宏的定义中,    $0 可以引用宏名,    $# 的展开结果是参数的个数。调用宏时,     空的括号      () 表示一个参数     ,例如:  
define(`count', ``$0': $# args') count        # -> count: 0 args count()      # -> count: 1 args count(1)     # -> count: 1 args count(1,)    # -> count: 2 args        $* 会被展开为参数列表。    $@ 也可以展开为参数列表,但是它会用引号来保护每个参数,保证它们不会在参数列表中被展开。例如:  
define(`list',`$`'*: $*; $`'@: $@') list(len(`abc'),`len(`abc')')         # -> $*: 3,3; $@: 3,len(`abc')    其中,    len 宏是 M4 内建的宏,用于统计字串的长度。  
显然,参数列表可以用于处理变长参数,其处理过程类似于 C99。下面定义了一个宏,它可以输出所接受的参数列表中最后一个参数:
define(`echolast', `ifelse(eval($#<2), 1, `$1`'', `echolast(shift($@))')') echolast(one,two,three)    # -> three    其中,    shift 是 M4 内建的宏,它的作用是对其所接受参数列表进行类似 Scheme 的    cdr 运算,也就是砍掉列表的首部,将剩下的发送到输出。由于    echolast 会被递归展开,于是它每一次递归展开都会被 shift 砍掉它所接受的参数列表的首部。当参数列表的长度为 1 时,    eval($# < 2) == 1 这个条件成立,因此递归过程所得结果就是    echolast 一开始所接受的参数列表中的最后的参数。  
如果我们需要『局部变量』该怎么做?也就是说,如何将一个宏只在另一个宏的定义中使用?局部宏的意义就类似于编程语言中的局部变量,如果没有局部宏,那么在一个全局的空间中,很容易出现宏名冲突,导致宏被意外的重定义了。
 为了避免宏名冲突,一种可选的方法是在宏名之前加前缀,比如使用    local 作为局部宏名的前缀。不过,这种方法对于递归宏无效。更好的方法是用    栈 。  
 M4 实际上是用一个栈来维护宏的定义的。当前宏的定义位于栈顶。使用    pushdef 可以将一个临时定义的宏压入栈中,在利用完这个临时的宏之后,再用    popdef 将其弹出栈外。例如:  
define(`USED',1) define(`proc',   `pushdef(`USED',10)pushdef(`UNUSED',20)dnl `'`USED' = USED, `UNUSED' = UNUSED`'dnl `'popdef(`USED',`UNUSED')') proc     # -> USED = 10, UNUSED = 20 USED     # -> 1    如果被压入栈的宏是未定义的宏,那么    pushdef 就相当于    define 。如果    popdef 弹出的宏也是未定义的宏,    popdef 就相当于    undefine ,它不会产生任何抱怨。  
 GNU m4 认为    define(X, Y) 与    popdef(X)pushdef(X, Y) 等价。其他的 m4 实现会认为    define(X) 等价于    undefine(X)define(X, Y) ,也就是说,新的宏的定义会更新整个栈。  
 GNU m4 在文本中遇到与内建宏同名的单词,例如    define ,它不会改变这个单词,除非这个按此后面紧跟着括号。例如:  
define(`MYMACRO',`text')    # -> define a macro              # -> define a macro    我们可以认为 m4 没有展开这个宏——但事实上它展开了,只不过是展开结果是宏名自身。我们也能让自己定义的宏具备这种能力,只加一个条件判断即可实现。例如,下面定义了的可逆转字符串的宏    reverse :  
define(`reverse',`ifelse($1,,,                   `reverse(substr($1,1))`'substr($1,0,1)')') reverse drawer: reverse(`drawer')     # ->  drawer: reward  define(`reverse',`ifelse($#,0,``$0'',$1,,,                   `reverse(substr($1,1))`'substr($1,0,1)')') reverse drawer: reverse(`drawer')     # -> reverse drawer: reward    m4 有一个    -P 选项,它可以强制性的在其内建宏名之前冠以    m4_ 前缀。例如下面的 M1.m4 文件:  
define(`M1',`text1')M1          # -> define(M1,text1)M1 m4_define(`M1',`text1')M1       # -> text1   直接用 m4 处理,结果为:
$ m4 M1.m4 text1          # -> define(M1,text1)M1 m4_define(M1,text1)text1       # -> text1    如果用    m4 -P 来处理,结果为:  
$ m4 -P test.m4 define(M1,text1)M1          # -> define(M1,text1)M1 text1       # -> text1