如何编写与#ifdef…#else…#endif C预处理器宏匹配的awk程序?

如何编写与#ifdef…#else…#endif C预处理器宏匹配的awk程序?,c,awk,c-preprocessor,preprocessor,C,Awk,C Preprocessor,Preprocessor,我有大量的C程序,它们有以下代码块 100. #ifdef DEBUG1 . . . 102. #else . . . 105. #endif 或者 此外,单个文件可能包含多个#DEBUG宏。我想提取与宏对应的行号。假设代码段中的数字是源文件中的行号,我希望输出采用以下格式: FILE - MACRO_NAME - IFDEF - ELIF - ENDIF ----------------------------------------

我有大量的C程序,它们有以下代码块

100. #ifdef DEBUG1
    .
    .
    .
102. #else    
    .
    .
    .
105. #endif
或者

此外,单个文件可能包含多个
#DEBUG
宏。我想提取与宏对应的行号。假设代码段中的数字是源文件中的行号,我希望输出采用以下格式:

FILE - MACRO_NAME - IFDEF - ELIF - ENDIF
----------------------------------------
prog.c - DEBUG1   -  100  -  102  -  105
prog.c - DEBUG2   -  200  -   X   -  206
我如何编写一个
awk
程序来实现同样的目标?如果
awk
不是合适的工具,我可以使用什么工具?

awk实际上有关联数组,因此我将采取以下方法:

  • 对于每个
    #ifdef
    (或类似变量,如
    #if 1
    ),增加一个变量,然后使用该变量存储
    if
    行号,将
    else
    endif
    行号设置为-1

  • 对于
    #else
    行,使用当前变量设置
    else
    行号

  • 对于
    #endif
    ,输出行号的任何详细信息,然后减小变量

  • 对于
    #elif
    ,您需要组合
    #else
    #if
    操作,并确保相关的
    #end
    关闭所有
    #if/#elif

  • 例如,下面是一个自包含的
    bash
    脚本,展示了它是如何工作的:

    #!/usr/bin/env bash
    
    # Use this script as input file as well, luckily C preprocessor
    # macros look like bash comments.
    
    #ifdef XYZZY
        # Some text inside the first ifdef
        #if 0
            # This is the inner bit.
        #endif
        #if 1
            # blah blah blah
        #elif defined TWISTY
            # yada yada yada
        #elif defined PASSAGES
            # run out of phrases
        #else
            # still got nothing
        #endif
    #else
        #ifdef PLUGH
            # This is the plugh stuff
        #else
            # This is the anti-plugh stuff
        #endif
    #endif
    
    awk <$0 '
        $1 == "#ifdef" || $1 == "#if" {
            level++
            line_mac[level] = $0
            gsub(/^[ \t]+/, "", line_mac[level])
            line_if[level] = NR
            line_else[level] = "X"
            line_end[level] = "X"
            typ_elif[level] = 0
            next
        }
        $1 == "#elif" {
            line_else[level] = NR
            level++
            line_mac[level] = $0
            gsub(/^[ \t]+/, "", line_mac[level])
            line_if[level] = NR
            line_else[level] = "X"
            line_end[level] = "X"
            typ_elif[level] = 1
            next
        }
        $1 == "#else" {
            line_else[level] = NR
            next
        }
        $1 == "#endif" {
            while (typ_elif[level] == 1) {
                printf "if-line %-4s, else-line %-4s, endif-line %-4s, macro '%s'\n", line_if[level], line_else[level], NR, line_mac[level]
                level--
            }
            printf "if-line %-4s, else-line %-4s, endif-line %-4s, macro '%s'\n", line_if[level], line_else[level], NR, line_mac[level]
            level--
        }
        '
    
    Awk实际上具有关联数组,因此我将采用以下方法:

  • 对于每个
    #ifdef
    (或类似变量,如
    #if 1
    ),增加一个变量,然后使用该变量存储
    if
    行号,将
    else
    endif
    行号设置为-1

  • 对于
    #else
    行,使用当前变量设置
    else
    行号

  • 对于
    #endif
    ,输出行号的任何详细信息,然后减小变量

  • 对于
    #elif
    ,您需要组合
    #else
    #if
    操作,并确保相关的
    #end
    关闭所有
    #if/#elif

  • 例如,下面是一个自包含的
    bash
    脚本,展示了它是如何工作的:

    #!/usr/bin/env bash
    
    # Use this script as input file as well, luckily C preprocessor
    # macros look like bash comments.
    
    #ifdef XYZZY
        # Some text inside the first ifdef
        #if 0
            # This is the inner bit.
        #endif
        #if 1
            # blah blah blah
        #elif defined TWISTY
            # yada yada yada
        #elif defined PASSAGES
            # run out of phrases
        #else
            # still got nothing
        #endif
    #else
        #ifdef PLUGH
            # This is the plugh stuff
        #else
            # This is the anti-plugh stuff
        #endif
    #endif
    
    awk <$0 '
        $1 == "#ifdef" || $1 == "#if" {
            level++
            line_mac[level] = $0
            gsub(/^[ \t]+/, "", line_mac[level])
            line_if[level] = NR
            line_else[level] = "X"
            line_end[level] = "X"
            typ_elif[level] = 0
            next
        }
        $1 == "#elif" {
            line_else[level] = NR
            level++
            line_mac[level] = $0
            gsub(/^[ \t]+/, "", line_mac[level])
            line_if[level] = NR
            line_else[level] = "X"
            line_end[level] = "X"
            typ_elif[level] = 1
            next
        }
        $1 == "#else" {
            line_else[level] = NR
            next
        }
        $1 == "#endif" {
            while (typ_elif[level] == 1) {
                printf "if-line %-4s, else-line %-4s, endif-line %-4s, macro '%s'\n", line_if[level], line_else[level], NR, line_mac[level]
                level--
            }
            printf "if-line %-4s, else-line %-4s, endif-line %-4s, macro '%s'\n", line_if[level], line_else[level], NR, line_mac[level]
            level--
        }
        '
    

    来扩展@paxdiablo的答案。如果您有
    gawk
    并使用多个文件作为输入,则可以利用
    BEGINFILE
    ENDFILE
    规则打印每个文件中的宏

    注意,与只在所有输入的开头和结尾运行一次的
    BEGIN/END
    块不同,这些块在每个文件的开头/结尾运行(不引人注意)

    因此,一个简化的脚本,忽略
    #else
    等,您可以添加额外的规则,下面这样的awk脚本可能对多个输入文件有用

    #!/usr/bin/awk -f
    
    BEGIN {
        printf "%-10s | %-10s | %-5s | %-5s\n", "FILE", "MACRO", "IFDEF", "ENDIF";
        print "----------------------------------------"
    }
    
    BEGINFILE {
        delete macros;
        delete locs;
        i = 0;
    }
    
    /^[ \t]*#ifdef[\t ]+([^ \t])+/ {
        macros[i++] = $2;
        locs[i]["start"] = FNR;
    }
    
    /^[ \t]*#endif/ {
        locs[--i]["end"] = FNR;
    }
    
    ENDFILE {
        for (i = 0; i < length(macros); i++) {
            printf "%-10s - %-10s - %-4d - %-4d\n", 
                FILENAME, macros[i], locs[i]["start"], locs[i]["end"];
        }
    }
    

    来扩展@paxdiablo的答案。如果您有
    gawk
    并使用多个文件作为输入,则可以利用
    BEGINFILE
    ENDFILE
    规则打印每个文件中的宏

    注意,与只在所有输入的开头和结尾运行一次的
    BEGIN/END
    块不同,这些块在每个文件的开头/结尾运行(不引人注意)

    因此,一个简化的脚本,忽略
    #else
    等,您可以添加额外的规则,下面这样的awk脚本可能对多个输入文件有用

    #!/usr/bin/awk -f
    
    BEGIN {
        printf "%-10s | %-10s | %-5s | %-5s\n", "FILE", "MACRO", "IFDEF", "ENDIF";
        print "----------------------------------------"
    }
    
    BEGINFILE {
        delete macros;
        delete locs;
        i = 0;
    }
    
    /^[ \t]*#ifdef[\t ]+([^ \t])+/ {
        macros[i++] = $2;
        locs[i]["start"] = FNR;
    }
    
    /^[ \t]*#endif/ {
        locs[--i]["end"] = FNR;
    }
    
    ENDFILE {
        for (i = 0; i < length(macros); i++) {
            printf "%-10s - %-10s - %-4d - %-4d\n", 
                FILENAME, macros[i], locs[i]["start"], locs[i]["end"];
        }
    }
    
    给定这样的内容应该就是您所需要的(使用GNU awk作为ENDFILE和argid):

    awk'
    {hit=0}
    $1==“#ifdef”{
    宏名称=$2
    计数[宏名称]++
    命中率=1
    }
    $1~/#(else | endif)$/{hit=1}
    hit{fnr[macroname,count[macroname],$1]=fnr;hit=0}
    结束文件{
    如果(ARGIND==1){
    打印“文件”、“宏名称”、“IFDEF”、“ELIF”、“ENDIF”
    }
    for(计数中的宏名称){
    对于(i=1;i给定这样的内容,您应该只需要(将GNU awk用于ENDFILE和argid):

    awk'
    {hit=0}
    $1==“#ifdef”{
    宏名称=$2
    计数[宏名称]++
    命中率=1
    }
    $1~/#(else | endif)$/{hit=1}
    hit{fnr[macroname,count[macroname],$1]=fnr;hit=0}
    结束文件{
    如果(ARGIND==1){
    打印“文件”、“宏名称”、“IFDEF”、“ELIF”、“ENDIF”
    }
    for(计数中的宏名称){
    
    对于(i=1;i它需要多健壮?例如,它需要能够忽略字符串和注释中的
    #ELIF
    语句吗?像
    #ifdef DEBUG1…#ifdef DEBUG2…#endif…#endif
    ?这样的嵌套定义如何?它不需要那么健壮。我很了解数据集。没有这种情况,即使它是这是非常罕见的。好吧,那么你的问题是至少包括一个简明的、可测试的样本输入文件和相关的预期输出的示例,这样我们就可以测试潜在的解决方案,以便我们可以帮助你。它需要有多强大?例如,它需要能够忽略内部的
    #ELIF
    语句吗字符串和注释?嵌套定义如
    #ifdef DEBUG1…#ifdef DEBUG2…#endif…#endif
    ?它不需要那么健壮。我很了解数据集。没有这种情况,即使有,也非常罕见。那么您的问题是至少包含一个简洁、可测试的示例输入文件和相关的预期输出,这样我们就可以测试潜在的解决方案,从而帮助您。Ed,您可以在我的回答中抓住脚本的前26行进行测试,这至少会让您对它的工作有一定的信心。@paxdiablo谢谢,但您的示例涵盖了我在评论和在她的数据中,所以我的脚本无法处理。我尝试了你的脚本,试图更好地理解它(我的awk-foo很弱),但得到了稍微错误的结果。它看起来可能很简单
    $ ./defs.awk tst.h tst2.h 
    
    FILE       | MACRO      | IFDEF | ENDIF
    ----------------------------------------
    tst.h      - DEBUG1     - 0    - 5   
    tst.h      - INNER1     - 1    - 4   
    tst2.h     - DEBUG2     - 0    - 3   
    
    awk '
    { hit = 0 }
    $1 == "#ifdef" {
        macroname = $2
        count[macroname]++
        hit = 1
    }
    $1 ~ /#(else|endif)$/ { hit = 1 }
    hit { fnr[macroname,count[macroname],$1] = FNR; hit = 0 }
    ENDFILE {
        if (ARGIND == 1) {
            print "FILE", "MACRO_NAME", "IFDEF", "ELIF", "ENDIF"
        }
        for (macroname in count) {
            for (i=1; i<=count[macroname]; i++) {
                print FILENAME, macroname, fnr[macroname,i,"#ifdef"]+0, fnr[macroname,i,"#elif"]+0, fnr[macroname,i,"#endif"]+0
            }
        }
        delete count
        delete fnr
    }
    ' *.c