這個問題的思考其實對於某一種語言而言,基本都能實現,只是簡單和復雜而已。而此次我討論就是只是在linux下面使用了shell和c對源代碼進行行 數的討論。本打算是實現一個python版本的,由於python這塊還不是太熟,所以就等以後熟了把這塊補上。
shell的強大快捷之處就在此體現出來了。我們使用find
命令就直接能將目標的文件進行檢索,然後我們就直接對檢索出來的對象進行統計。統計 我們知道使用wc
這個命令,但是我們觀察一下wc的輸出:
206 ./2014-03-09-jekyll-blog.md
前面是我們想要的內容,而後面的文件名卻不是我們要得,怎麼解決?一個思路就是將這個結果保存到一個文件中,然後使用cut
等命令對文件進行改造,接著再去統計,這個方法個人感覺比較麻煩。那麼第二個思路就是用awk
,直接對我們的結果進行分割。按照思路二,那我們就對此進行編碼了
這邊的$1
是路徑名 $2
指定的最大深度 $3
文件的後綴名或是一些正則的表達式,$3的這個設計我原先的打算是做一個test,根據用戶輸入,然後最後自己生成正則,感覺這樣有點多此一舉了。一個項目中使用的文件後綴一般不會很多,統計的是核心代碼,所以這一塊就可以簡便一點了。
這邊的file就是之前find的返回值,我們將上面的這個代碼放在一個循環中。這邊是awk的最簡單使用了,將一個記錄的第一個字段打印出來。但是不要混淆了這邊的$1
和shell腳本的中的$1
是不一樣的,這在awk的命令中,所以不要擔心會出錯。
我們為了讓輸出變得好看一點,對echo的輸出加一些修飾,主要是顏色的修飾:
echo -e "$s: \e[1;32m $t \e[0m"
echo -e "\e[1,31m total: \e[1;32m $w \e[0m"
好了,至此一個基本上能夠滿足要求的代碼統計的腳本就完成了,一個完整的版本可以到我的gist看。
之所以想到使用c來統計,是因為這是在linux平台上,可以使用系統編程方面的一些東西來解決這個問題。因為這個不在標准c中提供對目錄的操作 對於windows下面,我沒有去研究,所以不好多加敘述。
我們來看看如何實現C版本的統計程序?首先等解決的就是一個文件遍歷的問題,這個解決不了接下去的工作就不用考慮。好在在linux中提供了一個 dirent.h的頭文件,相關的函數都在這裡面。我們可以用的是opendir
readdir
telldir
seekdir
closedir
著幾個函數,這寫函數基本就是差不多了,還缺的一個就是更改目錄的函數chdir
這個函數的實現在unistd.h
中,所以這些基本上就齊了。
readdir返回的是一個結構體,結構體中有一個參數d_name,這是保存的文件的名字,所以有了這個,我們就可以對其進行屬性的判斷了。因為我們要判斷這是文件還是目錄。
如何判斷?沒有shell中if [ -d filename ]
這樣快捷的判斷,但是我們要想到的一點就是,這是在liunx中,一切皆文件,對於文件的屬性的判斷,我們可以利用lstat()
這個函數是在sys/stat.h
中,我們使用lstat(filename,&statbuf)
然後就可以用宏還進行判斷了S_ISDIR(statbuf.st_mode)
. 這樣就解決了文件類型的判斷。
接著我們來考慮如何來遍歷一個文件夾呢?首先的著肯定是在一個循環下。因為一個目錄下有時不止一個文件,但是對於不同的目錄的時候呢?例如一個目錄下的一個子目錄,如何去實現遍歷?我們考慮到的是使用遞歸,遞歸調用我們寫的那個函數。因為每次讀取的目錄只有一個。而且當readdir
讀到文件尾的時候回返回一個NULL。 也許你會問這個遞歸是如何實現的。我們在循環結束後,調用了chidr("..")
回到上層,這樣就繼續遍歷。這就是遞歸終止的條件。
是時候統計代碼了,我們剔除了目錄的文件,剩下的就是真正的文件了。對於統計使用fgets來實現遍歷一個文件,然後加個循環就解決問題。這邊一個不好的就是沒有辦法去判斷文件的類型,統計是對目錄下所以的文件進行的統計,這一點不如shell的靈活。
下面是源代碼,基本的思路就是這樣,還要注意的一個細節是有兩個特殊的目錄就是.
和..
我們要忽略掉,不然會產生一個死循環。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <dirent.h> /*some functions option on dir*/ #include <sys/stat.h> #define MAXLINE 200 int linecount=0; /*for save the countline*/ int getLine(char *fname) { FILE *fp; char line[MAXLINE]; int total = 0; fp = fopen(fname,"r"); while(fgets(line,200,fp) != NULL) total++; return total; } /*Recurse a the target dirent*/ void Recur_dir(const char *dir, int depth) { DIR *dp; /*dir pointer*/ struct dirent *entry; /*structure to save dir info*/ struct stat statbuf; /*use to charge which is dir and which is file*/ /*get the decriptor*/ if((dp = opendir(dir)) == NULL) { printf("cannot open directory:%s\n",dir); fprintf(stderr,"opendir error:%s\n",strerror(errno)); exit(1); } chdir(dir); /*enter into target dir*/ while((entry = readdir(dp)) != NULL) { lstat(entry->d_name,&statbuf);/*get the entry status*/ if(S_ISDIR(statbuf.st_mode)) { /*is a dirctory and ignore the .. and .*/ if(strcmp(".",entry->d_name)== 0 || strcmp("..",entry->d_name) == 0) continue; //printf("%*s%s\n",depth,"",entry->d_name); Recur_dir(entry->d_name,depth+4); /*continue to open */ } else /*if not a dirctroy and we count the line*/ { linecount += getLine(entry->d_name); /*count everyfile line*/ } } chdir(".."); closedir(dp); } int main(int argc, const char *argv[]) { char *topdir = "." if(argc > 2) { printf("Usage:%s dir\n",argv[0]); exit(1); } if(argc == 2) { topdir = argv[1]; } Recur_dir(topdir,0); printf("Total:%d\n",linecount); return 0; }
如果不提供參數,就遍歷當前目錄,提供了就統計給出的目錄。