用grep统计代码行数


昨天突然有个需求:统计一下源文件中代码的行数和注释的行数。
由于手头上没有别的工具,所以首先想到的就是用grep搜了。

关于正则表达式的基本用法,以前写过一篇记录,这里就不重复了。
先来预习一下几个必要的grep命令开关和扩展正则表达式用法。

-c,–count 只打印匹配的行数,不显示匹配的内容
-n,–line-number 在匹配的行前面打印行号
-v,–revert-match 只显示不匹配的行,也就是反条件搜索
-E,–extended-regexp 正则表达式扩展集匹配模式,即ERE(egrep)支持模式。下文正则表达式中用到或运算符(|)时,必须打开这个开关。


另外,我们还会用到一两个POSIX字符类(POSIX Character Class),要想查看全部的内容,可以参考Wikipedia

[:space:] 包括空格和制表符之类的空白字符
[:print:] 可见字符和空格
[:graph:] 仅可见字符

最后,使用grep的时候正则表达式写在两个’符号之间,然后敲入文件名。
不写文件名,或者文件名部分是-的时候,grep使用标准输入。
接着我们检查一下源代码文件,会发现注释的部分有:
/*
□*
■■■■/*
□□□□/*
为了方便说明,这里□表示空格,■■■■表示一个TAB产生的相当于4个空格位的缩进,自然□□□□就是四个空格产生的缩进了。
至于与代码在同一行添加注释的情况,我们把该行仅当作代码行进行统计。

OK,先来看一下实验用的源代码,一份很普通的HelloWorld程序,文件名就叫HelloWorld.c。
文件头尾部包含了一些CVS自动生成的文件信息和版本信息。

/*
 * @(#)$Id: HelloWorld.c,v 1.0 2011/01/28 00:00:18 CookieBear.info Exp $
 * @(#) A simple program which prints 'Hello World.' on your screen.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main (int argc, char **argv)
{
	/* TODO: type your code starting from here */

	/* Call a standard library function to print a specified string. */
	printf("Hello, world."); /* inline comment */
	
	return 0;
}

/*=====================================================================*/
/*
 * [Update Info]
 *
 * $Log: HelloWorld.c,v $
 * Revision 1.1 2010/01/28 00:00:18  CookieBear.info
 * The first version of this program.
 *
 */
/*=====================================================================*/

第一步,统计所有的注释行,命令如下:
grep -En ‘^\/\*|^[[:space:]]\*|^(+[[:space:]])\/\*‘ HelloWorld.c
绿色部分是用来提取行首以/*开始的注释行。
蓝色部分是用来提取行首以□*开始的注释行。
橙色部分是用来提取行首以不定数□或■加上/*组成的注释行。

下面是执行结果:

1:/*
2: * @(#)$Id: HelloWorld.c,v 1.0 2011/01/28 00:00:18 CookieBear.info Exp $
3: * @(#) A simple program which prints 'Hello World.' on your screen.
4: */
11:	/* TODO: type your code starting from here */
13:	/* Call a standard library function to print a specified string. */
19:/*=====================================================================*/
20:/*
21: * [Update Info]
22: *
23: * $Log: HelloWorld.c,v $
24: * Revision 1.1 2010/01/28 00:00:18  CookieBear.info
25: * The first version of this program.
26: *
27: */
28:/*=====================================================================*/

嗯,预料之中的结果。如果在这里把开关符n替换成c,那就只显示匹配的行数了。

下一步,统计所有的代码行:
按照不是注释行以外的部分就是代码行的思路,输入下面的命令
grep -Evn ‘^\/\*|^[[:space:]]\*|^(+[[:space:]])\/\*’ HelloWorld.c

结果是:

5:#include <stdio.h>
6:#include <stdlib.h>
7:#include <string.h>
8:
9:int main (int argc, char **argv)
10:{
12:
14:	printf("Hello, world."); /* inline comment */
15:	
16:	return 0;
17:}
18:

等等,结果里混入了空行。怎么把他们去除呢?

在想办法去除空行前,我们先想想怎样把空行单独列出来。
统计所有空行(不包含空格)
grep -vn ‘[[:print:]]’ *.c
不带-v开关是寻找所有包含非控制符(也就是A-Za-z0-9以及各种符号并包括空格)的行。
带了-v开关后则是寻找所有不包含非控制符的行,也就是空行。
统计所有空行(包含空格)
grep -vn ‘[[:graph:]]’ *.c
上述同理,除了对空格的判别有些不同外。

那么把前后两条命令的正则表达式合并起来可以吗?像下面那样:
grep -Evn ‘(^\/\*|^[[:space:]]\*|^(+[[:space:]])\/\*)&[[:graph:]]’ HelloWorld.c
很遗憾,2个正则表达式只能做或运算,而不能做与运算。

冷静回想一下,我们要做的其实只是在最初的结果上把空行去除就可以了。
那么完全可以用管道的方式把第一个grep的输出连接到第二个grep的输入。
输入下面的命令:
grep -Ev ‘^\/\*|^[[:space:]]\*|^(+[[:space:]])\/\*’ HelloWorld.c | grep -n ‘[[:graph:]]’ –
得到结果:

1:#include <stdio.h>
2:#include <stdlib.h>
3:#include <string.h>
5:int main (int argc, char **argv)
6:{
8:	printf("Hello, world."); /* inline comment */
10:	return 0;
11:}

最后,如想得到匹配行数,一定要将第二个grep的n开关符替换成c开关符。而不应该去修改第一个grep的部分。

感觉自己对正则表达式的理解还很浅薄,如果您觉得有更好的写法的话,请一定要在回复里告诉我。

本文所有命令在MacOS X Snow Leopard和Cygwin下测试通过。

4 thoughts on “用grep统计代码行数

  1. weiwei

    兄弟,没事吧?
    等一切安定了,有空报个平安吧

    未未

  2. MrBear Post author

    谢谢各位,一切都好,就是余震不断,不敢睡死。。。

  3. newptone

    find -d -name ‘*.cpp’ |xargs wc -l 可以统计一个目录下的每个cpp文件有多少行代码,以及代码行数的总和。
    博客样式很舒服~

发表评论

电子邮件地址不会被公开。