昨天突然有个需求:统计一下源文件中代码的行数和注释的行数。
由于手头上没有别的工具,所以首先想到的就是用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自动生成的文件信息和版本信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /*
* @(#)$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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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
结果是:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 2 3 4 5 6 7 8 | 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下测试通过。