如何从C源文件中删除所有的/* */注释?

14

我有一个C文件,是从其他地方复制来的,但它有很多像下面这样的注释:

int matrix[20];
/* generate data */
for (index = 0 ;index < 20; index++)
matrix[index] = index + 1;
/* print original data */
for (index = 0; index < 5 ;index++)
我该如何删除所有被 /**/ 包含的注释?有时,注释可能由 4-5 行组成,我需要删除所有这些行。基本上,我需要删除在 /**/ 之间的所有文本,甚至包括 \n。请帮我使用 sedawkperl 中的一个完成此操作。

12
我喜欢在句子 "I have a C file, but it has a lot of comments" 中的“but”这个词。 - innaM
@Manni ;-) 请查看 http://stackoverflow.com/questions/1260273/am-i-being-unreasonable-in-rejecting-candidates-with-poor-spelling-and-grammar/1260285#1260285 - Sinan Ünür
3
毫无疑问,有些评论很疯狂。但是要摆脱 所有 评论吗? - innaM
10个回答

32
为什么不使用c预处理器来实现这个功能?为什么要限制自己使用自制的正则表达式?
[编辑]该方法还可以清晰地处理Bart的printf(".../*...")场景。
示例:
[File: t.c]
/* This is a comment */
int main () {
    /* 
     * This
     * is 
     * a
     * multiline
     * comment
     */
    int f = 42;
    /*
     * More comments
     */
    return 0;
}

.

$ cpp -P t.c
int main () {







    int f = 42;



    return 0;
}

或者您可以删除空格并压缩所有内容。
$ cpp -P t.c | egrep -v "^[ \t]*$"
int main () {
    int f = 42;
    return 0;
}

没有必要重新发明轮子,不是吗?

[编辑] 如果您希望通过此方法不扩展包含的文件和宏,则cpp提供了相应的标志。请考虑:

[文件:t.c]

#include <stdio.h>
int main () {
    int f = 42;
    printf("   /*  ");
    printf("   */  ");
    return 0;
}

.

$ cpp -P -fpreprocessed t.c | grep -v "^[ \t]*$"
#include <stdio.h>
int main () {
    int f = 42;
    printf("   /*  ");
    printf("   */  ");
    return 0;
}

需要注意的是,宏扩展可以被避免,但原始宏定义将从源代码中删除。


4
预处理器具有一个(潜在的不良)“副作用”:它还会处理宏、包含已包含的文件等等。 - Raphaël Saint-Pierre
4
您可以通过使用“-fpreprocessed”选项来取消宏展开。我会进行更新以提到这一点。 - ezpz
1
再次输出-1。如果您希望在删除注释后源代码能够编译,那么这不是一个轻微的警告。 - Sinan Ünür
这个警告可以通过以下方式解决:perl -wpe 's/^\s*#define/#include#define/' your-file.c |cpp -P - -fpreprocessed|perl -wpe 's/#include#define/#include/' ---- 这将把任何 #defines 转换成(有些无效的)#includes,以便通过预处理器传递,稍后再转换回正确的 #defines。(如果您同意,请将此添加到答案本身中)。 - Yaakov Belch
这样做非常好:grep -v -E '^#' tutorial.thrift |cpp -P - schemacs
@ed-morton的回答难道不是这个问题更完整的版本吗? https://dev59.com/i3I-5IYBdhLWcg3wta1q#13062670 - lpacheco

12

请参考perlfaq6。这是一个相当复杂的场景。

$/ = undef;
$_ = <>;
s#/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
print;

警告一句,一旦你这样做了,你有测试场景来证明你只是删除了注释而没有删除任何有价值的东西吗?如果你正在运行这样一个强大的正则表达式,我建议你进行某种形式的测试(即使只是在之前/之后记录行为)。


只需检查通过编译创建的二进制文件是否相同(除了时间戳或其他构建标识)。 - ephemient
那可能是最简单的解决方案。 - Brian Agnew
1
同意,如果我没有编写单元测试来验证过滤后的正确性,我绝不会在我关心的代码上这样做。 - Ether

6

看一下Inline::Filters中的strip_comments例程:

sub strip_comments {
    my ($txt, $opn, $cls, @quotes) = @_;
    my $i = -1;
    while (++$i < length $txt) {
    my $closer;
        if (grep {my $r=substr($txt,$i,length($_)) eq $_; $closer=$_ if $r; $r}
        @quotes) {
        $i = skip_quoted($txt, $i, $closer);
        next;
        }
        if (substr($txt, $i, length($opn)) eq $opn) {
        my $e = index($txt, $cls, $i) + length($cls);
        substr($txt, $i, $e-$i) =~ s/[^\n]/ /g;
        $i--;
        next;
        }
    }
    return $txt;
}

5
请不要使用cpp,除非您理解其影响:
$ cat t.c
#include <stdio.h>

#define MSG "Hello World"

int main(void) {
    /* ANNOY: print MSG using the puts function */
    puts(MSG);
    return 0;
}

现在,让我们用cpp运行它:
$ cpp -P t.c -fpreprocessed


#include <stdio.h>



int main(void) {


    puts(MSG);
    return 0;
}

显然,这个文件不再能够编译。


好的,无论如何在您添加“-fpreprocessed”标志之后,都不会这样。 - Hasturkun
@Hasturkun 如果你不加上 -fpreprocessed 参数,#include <stdio.h> 将会被展开。 - Sinan Ünür
我尝试过这个命令:perl -wpe 's/^\s*#define/#include#define/' your-file.c |cpp -P - -fpreprocessed|perl -wpe 's/#include#define/#include/' ---- 这将任何 #define 转换成(有些无效的)#include,通过预处理器进行传递,稍后再转换回正确的 #define。 - Yaakov Belch

4

在处理特定预处理器功能(例如扩展#define或#include)时,您必须使用C预处理器与其他工具组合使用,并且所有其他方法都会在边缘情况下失败。这将适用于所有情况:

[ $# -eq 2 ] && arg="$1" || arg=""
eval file="\$$#"
sed 's/a/aA/g;s/__/aB/g;s/#/aC/g' "$file" |
          gcc -P -E $arg - |
          sed 's/aC/#/g;s/aB/__/g;s/aA/a/g'

将其放入一个shell脚本中,然后使用想要解析的文件名称调用该脚本,可选择添加前缀为“-ansi”的标志来指定应用的C标准。


1
我怀疑其他发布的解决方案看起来更简单,但是这个解决方案始终能够起作用,而其他解决方案只有在某些情况下才能起作用。那些尝试其他解决方案的人还没有遇到那些自己选择的解决方案失败的情况(或者还没有注意到失败)。啊,看来我是在原问题发布3年后才发布了这个答案,这可能是一个重要因素! - Ed Morton
1
我记得很久以前做过类似的事情。昨天我需要再次使用它来快速完成某件事,而且我知道其他答案不能涵盖所有情况。我希望我能再次点赞它! - Sam

4

请考虑以下内容:

printf("... /* ...");
int matrix[20];
printf("... */ ...");

换句话说:除非您进行一次替换并确信上述情况不会发生,否则不要使用正则表达式来完成此任务。

3
在命令行中尝试以下操作(将“file-names”替换为需要处理的文件列表):
perl -i -wpe 'BEGIN{undef $/} s!/\*.*?\*/!!sg' file-names

这个程序会直接修改文件(用更正后的输出覆盖原始文件)。如果你只想得到输出而不改变原始文件,请省略“-i”开关。
解释:
perl -- call the perl interpreter
-i      switch to 'change-in-place' mode.
-w      print warnings to STDOUT (if there are any)
 p      read the files and print $_ for each record; like while(<>){ ...; print $_;}
 e      process the following argument as a program (once for each input record)

BEGIN{undef $/} --- process whole files instead of individual lines.
s!      search and replace ...
  /\*     the starting /* marker
  .*?     followed by any text (not gredy search)
  \*/     followed by the */ marker
!!      replace by the empty string (i.e. remove comments)  
  s     treat newline characters \n like normal characters (remove multi-line comments)
   g    repeat as necessary to process all comments.

file-names   list of files to be processed.

@brian 已接受:这只是一个大致的解决方案。 - Yaakov Belch

1
尝试以下递归方式查找和删除Java脚本类型注释、XML类型注释和单行注释。
/* This is a multi line js comments.

Please remove me*/

对于find pages/ -name "*.*"中的每个文件,执行以下操作:
使用Perl在文件中删除所有注释(包括多行注释)并保存修改。

<!-- This is a multi line xml comments.

Please remove me -->

对于在查找页面/ -name“*.*”中的每个f,执行以下操作:使用perl进行-i -wpe编辑,并在BEGIN块中undef$/。将匹配到的<!--.*?-->替换为空字符串,在$f上执行此操作。

//This is single line comment Please remove me.

对于find pages/ -name "*.*"中的每个文件,运行sed -i 's///.*//'命令以删除注释。

注意:pages是根目录,上述脚本将在根目录和子目录中查找并删除所有文件中的注释。


1

当我需要CSS的简短和简单的东西时,我使用这个:

awk -vRS='*/' '{gsub(/\/\*.*/,"")}1' FILE

这种方法无法处理注释分隔符出现在字符串内部的情况,但比解决方案简单得多。显然,它并非万无一失或适用于所有情况,但您比SO上的学究更清楚是否可以接受。

我相信这个链接万无一失的。


0

这是一个使用gawk的非常简单的例子。在实施之前,请多次测试。当然,它不会处理其他注释风格//(在C++中??)

$ more file
int matrix[20];
/* generate data */
for (index = 0 ;index < 20; index++)
matrix[index] = index + 1;
/* print original data */
for (index = 0; index < 5 ;index++)
/*
function(){
 blah blah
}
*/
float a;
float b;

$ awk -vRS='*/' '{ gsub(/\/\*.*/,"")}1' file
int matrix[20];


for (index = 0 ;index < 20; index++)
matrix[index] = index + 1;


for (index = 0; index < 5 ;index++)


float a;
float b;

由于某些原因,这在我的机器上无法工作:(猫测试 int matrix[20]; / 生成数据 / for (index = 0; index <20; index ++) matrix [index] = index + 1; / 打印原始数据 /输出为 awk -vRS ='/' '{gsub(//*./,"")} 1'测试 int matrix [20]; / 生成数据 / for (index = 0; index <20; index ++) matrix [index] = index + 1; / 打印原始数据 / - Vijay
抱歉,注释有些混乱,我没有注意到你已经输出了。好的,它对我有效。我看你仍然有 /生成数据/ 和 /打印原始数据/。从我的输出中可以看出,它对我有效。 - ghostdog74
如果你仍然无法使其工作,下面有一个Perl解决方案可以尝试。 - ghostdog74

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接