如何用m4实现类似cpp的条件代码包含功能?求成熟方案
Great question! Using m4 as a drop-in preprocessor for script languages that lack native conditional code inclusion is a clever approach—especially since cpp struggles with non-C syntax, and m4’s ubiquity makes it a safer choice than more niche tools like gpp.
The core challenge here is handling nested #if/#else/#endif blocks without relying on ifelse() (which falls apart with long or quote-heavy code). Using divert() to toggle output is the right direction, and tracking nested levels requires a simple stack implementation in m4 macros.
A Proven, Idiomatic m4 Implementation
This is a common pattern in m4 scripting—here’s a minimal, reusable implementation that handles nested conditions and output diversion:
Step 1: Initialize the Condition Stack
We’ll use a comma-separated string to track the active state of each nested condition (1 = active/output, 0 = inactive/suppressed):
dnl Initialize stack: top-level code is always active define(`COND_STACK', `,1') dnl Helper macro to get the current active state (top of the stack) define(`_current_active', `substr(COND_STACK, rindex(COND_STACK, `,')+1)')
Step 2: Implement the #if Macro
This pushes a new condition onto the stack, respecting the parent level’s active state (if the parent is inactive, the child will be too):
define(`if', `pushdef(`__cond', eval($1))dnl pushdef(`__parent_active', _current_active)dnl dnl Only activate this level if parent is active AND condition is true ifelse(eval(__parent_active && __cond), 1, `define(`COND_STACK', COND_STACK`,1')', `define(`COND_STACK', COND_STACK`,0')')dnl popdef(`__parent_active')dnl popdef(`__cond')dnl dnl Switch diversion: output only if current level is active ifelse(_current_active, 0, `divert(9)', `divert(0)')')
Step 3: Implement the #else Macro
This flips the active state of the current nested level (again, respecting parent state):
define(`else', `pushdef(`__stack', COND_STACK)dnl pushdef(`__parent_part', substr(__stack, 0, rindex(__stack, `,')))dnl pushdef(`__current_top', substr(__stack, rindex(__stack, `,')+1))dnl dnl Flip the current state, but only if parent is active pushdef(`__new_top', eval(substr(__parent_part, 2) && (1 - __current_top)))dnl define(`COND_STACK', __parent_part`'__new_top')dnl popdef(`__new_top')dnl popdef(`__current_top')dnl popdef(`__parent_part')dnl popdef(`__stack')dnl ifelse(_current_active, 0, `divert(9)', `divert(0)')')
Step 4: Implement the #endif Macro
This pops the top condition from the stack and updates the output diversion:
define(`endif', `pushdef(`__stack', COND_STACK)dnl define(`COND_STACK', substr(__stack, 0, rindex(__stack, `,')))dnl popdef(`__stack')dnl ifelse(_current_active, 0, `divert(9)', `divert(0)')')
Step 5: Optional: Fix Quote Conflicts
If your target script uses backticks or single quotes, redefine m4’s quote delimiters to avoid parsing issues:
changequote([,]) # Use [ and ] as m4's quote characters instead of ` and '
Testing the Implementation
Here’s how your example would work with these macros:
define(`something', 1) define(`something_else', 0) #if something some long code block #else alternate code block #if something_else do more stuff #endif #endif
Running m4 yourscript.m4 will output:
some long code block
Why This Works
- Stack Tracking: The
COND_STACKstring keeps track of every nested condition’s active state, ensuring we handle deep nesting correctly. - Diversion Control:
divert(9)sends inactive code to a discardable channel (m4 ignores channels 1-9 by default), whiledivert(0)sends active code to standard output. - Parent State Respect: Child conditions only activate if their parent level is active, mimicking cpp’s behavior exactly.
This is a standard idiom in m4—you’ll find similar stack-based logic in tools like GNU Autoconf, which uses m4 extensively for conditional checks. It’s lightweight, self-contained, and avoids reinventing the wheel unnecessarily.
内容的提问来源于stack exchange,提问作者Targeloid

