【问题标题】:How do I write an awk program that matches #ifdef...#else...#endif C pre-processor macros?如何编写与#ifdef...#else...#endif C 预处理器宏匹配的 awk 程序?
【发布时间】:2019-08-31 08:42:17
【问题描述】:

我有大量的 C 程序语料库,其中包含以下代码块。

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

或者,

200. #ifdef DEBUG2
    .
    .
    .
206. #endif

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

FILE - MACRO_NAME - IFDEF - ELIF - ENDIF
----------------------------------------
prog.c - DEBUG1   -  100  -  102  -  105
prog.c - DEBUG2   -  200  -   X   -  206

我怎样才能编写一个awk 程序来达到同样的效果?如果awk 不是合适的工具,我可以使用什么工具?

【问题讨论】:

  • 它需要有多强大?例如,它是否需要能够忽略字符串和 cmets 中的 #ELIF 语句?像#ifdef DEBUG1 ... #ifdef DEBUG2 ... #endif ... #endif 这样的嵌套定义怎么样?
  • 它不需要那么健壮。我很了解数据集。没有这种情况,即使有,也极为罕见。
  • 好的,然后edit 您的问题至少包含一个简洁、可测试的样本输入文件的示例和相关的预期输出,因此我们有一些东西可以测试潜在的解决方案,以便我们为您提供帮助。

标签: c awk c-preprocessor preprocessor


【解决方案1】:

鉴于 your comment 应该是您所需要的一切(使用 GNU awk 处理 ENDFILE 和 ARGIND):

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

当然,它未经测试,因为您没有提供我们可以测试的示例。设置OFS 或使用printf 或管道到column 以获得不同格式的输出,如果您愿意的话。

【讨论】:

  • Ed,您可以在我的答案中获取脚本的前 26 行进行测试,这至少应该让您对它的工作有一定的信心。
  • @paxdiablo 谢谢,但您的示例涵盖了我在评论中询问的案例和她的数据中的the OP said don't exist,因此我的脚本无法处理。
  • 我正在尝试您的脚本以更好地理解它(我的 awk foo 很弱),但得到的结果略有错误。看起来它可能是一个简单的东西,比如一个非 1 的东西。
  • 我很好奇,与 awk 中的多维数组相比,您通常更喜欢奇怪的数组索引连接吗?这对性能有很大提升吗?
  • @jenesaisquoi 我在选择使用哪个数组索引时不考虑性能,只考虑可移植性和简洁性。 arr[x,y] 将在任何 awk 中工作,而 arr[x][y] 仅适用于 gawk,因此我使用前者,除非有理由使用后者,例如它使代码明显更简洁。
【解决方案2】:

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

注意,与 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"];
    }
}

应该输出如下内容,

$ ./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   

【讨论】:

    【解决方案3】:

    Awk 实际上 具有 关联数组,所以我将采取的方法是:

      1234563 .
    1. 对于#else 行,使用当前变量设置else 行号。

    2. 对于#endif,输出行号的任何详细信息,然后递减变量。

    3. 对于#elif,您需要结合#else#if 操作并确保相关的#end 关闭所有#if/#elif 行。 p>

    例如,这是一个独立的 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--
        }
        '
    

    该输出(带有用于检查的文件中的编号行)是:

     1: #!/usr/bin/env bash
     2: 
     3: # Use this script as input file as well, luckily C preprocessor
     4: # macros look like bash comments.
     5: 
     6: #ifdef XYZZY
     7:     # Some text inside the first ifdef
     8:     #if 0
     9:         # This is the inner bit.
    10:     #endif
    11:     #if 1
    12:         # blah blah blah
    13:     #elif defined TWISTY
    14:         # yada yada yada
    15:     #elif defined PASSAGES
    16:         # run out of phrases
    17:     #else
    18:         # still got nothing
    19:     #endif
    20: #else
    21:     #ifdef PLUGH
    22:         # This is the plugh stuff
    23:     #else
    24:         # This is the anti-plugh stuff
    25:     #endif
    26: #endif
    
    if-line 8   , else-line X   , endif-line 10  , macro #if 0
    if-line 15  , else-line 17  , endif-line 19  , macro #elif defined PASSAGES
    if-line 13  , else-line 15  , endif-line 19  , macro #elif defined TWISTY
    if-line 11  , else-line 13  , endif-line 19  , macro #if 1
    if-line 21  , else-line 23  , endif-line 25  , macro #ifdef PLUGH
    if-line 6   , else-line 20  , endif-line 26  , macro #ifdef XYZZY
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-12
      • 1970-01-01
      • 2021-10-29
      • 2021-10-12
      • 2021-05-15
      • 2012-09-28
      相关资源
      最近更新 更多