实验二 目录树的遍历
背景
正确理解程序4-22。程序4-22递归降序遍历目录层次结构,并按照文件类型进行计数。主要涉及到三个函数myftw()、dopath()和myfunc()。myftw ()函数以所带参数pathname为要遍历的起始目录,计算出该目录下各种不同类型的文件的个数和所占百分比,并显示出来。它调用了另外两个函数,一个是dopath()函数,这是一个递归函数,对指定的起始目录下的每个目录项,按深度优先进行遍历;而对所访问的节点,则调用myfunc()进行处理。main函数输出统计结果。三个函数的参数含义如下:
(1) static int myftw(char *pathname, Myfunc *func);
pathname给出指要遍历开始的目录。
func是Myfunc类型的函数指针,定义访问的实际操作。
(2) static int dopath(Myfunc *func);
pathname给出指要遍历开始的目录。
(3) static int myfunc(const char *pathname, const struct stat *statptr, int type);
pathname指向当前访问节点的路径名。
statptr指向当前访问节点的i-节点的结构,该结构保存有许多该文件的信息。
type给出当前访问节点的类型,在实验中可以自己定义它的含义。
myfunc()的返回值通常是0,实际上在程序4-22中它的值总是0。但是在dopath()函数中,myfunc()的返回值非0意味着终止遍历。
另外,程序4-22中用到的函数path_alloc()用于分配存放路径名的内存空间。
实验内容和实现思路
实验二要求根据用户输入的命令行选项的不同,来实现三种功能:
一、
(1) argc为2时,命令格式为
myfind
它除了实现程序4-22原本的功能外,还要统计出,在常规文件中,文件长度不大于4096字节的常规文件,在所有允许访问的普通文件中所占的百分比。程序也不允许打印出任何路径名。这个功能实现比较简单,只要略加修改myfunc()和main()这两个函数就可以了。
实现思路:这一问比较简单,只要再myfunc函数中增加对文件大小的判断即可,这里使用参数里的Stat结构体里的Statptr->st_size(文件的大小)来跟4096比,不大于则统计量加1,其它文件格式也做相应的处理。修改后的myfunc函数如下:
static int myfunc(const char *pathname, const struct stat *statptr, int type)
{
switch(type)
{
case FTW_F:
switch( statptr->st_mode & S_IFMT )
{
case S_IFREG:
//实现2-(1)功能 统计出,在常规文件中,文件长度不大于4096字节的常规文件
if(statptr->st_size<4096) size++;break;
nreg++;
printf("reg: %s\n", fullpath);
break;
case S_IFBLK:
nblk++;
printf("blk: %s\n", fullpath);
break;
case S_IFCHR:
nchr++;
printf("chr: %s\n", fullpath);
break;
case S_IFIFO:
nfifo++;
printf("fifo: %s\n", fullpath);
break;
case S_IFLNK:
nslink++;
printf("slink: %s\n", fullpath);
break;
case S_IFSOCK:
nsock++;
printf("socket: %s\n", fullpath);
break;
case S_IFDIR:
fprintf(stderr, "For S_IFDIR for %s\n", pathname);
exit(1);
}
//书中没有输出遍历的结果,这个是我自己加上去的
break;
case FTW_D:
ndir++;
printf("DIR: %s\n", fullpath);
break;
case FTW_DNR:
fprintf(stderr, "can't read directory %s\n", pathname);
break;
case FTW_NS:
fprintf(stderr, "stat error for %s\n", pathname);
break;
default:
fprintf(stderr, "unkown type %d for pathname %s\n",
type, pathname);
}
return 0;
}
执行./work2 /etc 和 ./work2 ./后的结果
二、
(2) argc为4且argv[2] == “-comp”时,命令格式为
myfind -comp
它的功能是输出在目录子树之下,所有与文件内容一致的文件的绝对路径名。不允许输出任何其它的路径名,包括不可访问的路径名。为提高程序效率,在比较文件是否相同时,可先比较两个文件的大小,如果大小不同,则内容肯定不同,这样就免去了读文件所浪费的时间;如果大小相同,则再通过读文件进行比较。此时应当注意输入缓冲区不必开的太大,你可以从实验一得到启发。由于要求输出符合要求的文件的绝对路径名,因此当参数pathname不是绝对路径时,要调用getcwd()等函数来取得文件的绝对路径名。
实现思路:用一个函数myfunc2(const char * pathname,const struct stat *statptr,int type)来实现该功能。首先用全局变量dsize和dbuf保存待比较文件的大小和内容,对传进来的参数pathname进行判断它不是目录,否则不做处理。用文件pathname的i节点statptr->st_size和length比较,不等则两文件内容肯定不会相同。大小相等则对两文件的内容进行比较,开辟一个大小为dsize的缓冲区buf,将pathname的文件写入buf,然后用strcmp进行比较。这里有一个问题,绝对路径需要转化成相对路径,myfunc2()函数这里我调用<stdlib.h>里的realpath函数,很方便将绝对路径转化为相对路径。而在下一个函数myfunc3里,我将用另外一种方法进行转换。
myfunc2函数如下:
static int myfunc2(const char *pathname, const struct stat *statptr, int type)
{
int fd,len;
char *buf,*fullpathname; //存放文件内容和文件名称,用于和dbuf比较内容
// printf("pathname = %s\n",pathname);
buf=(char *)malloc(sizeof(char)*dsize);
fullpathname=malloc(len);
if(type==FTW_F)
{
if(dsize==statptr->st_size) //首先比较大小,如果大小不同,就不需要再比较
{
if((fd=open(pathname,O_RDONLY))==-1)
printf("cant't openfile\n");
read(fd,buf,dsize);
//文件内容相同
if(strcmp(dbuf,buf)==0)
{
//获取文件的绝对路径名
// getcwd(fullpathname,len);
count++;
char actualPath[100] = {0};
char* ptrRet = NULL;
ptrRet = realpath(pathname,actualPath);
printf("%s\n",actualPath);
}
close(fd);
}
}
return(0);
}
运行./work2 /home/linli -comp work2.c和./work2 …/…/ -comp work2.c的结果如下:
三、
(3) argc 大于等于4且argv[2] == “-name”时,命令格式为
myfind -name …
…是一个以空格分隔的文件名序列(不带目录)。命令输出在目录子树之下,所有与…序列中文件名相同的文件的绝对路径名。不允许输出不可访问的或无关的路径名。实现方法可以通过循环,把当前遍历的文件名和这个序列中的文件名进行比较,如果和序列中的一个文件名相同,就符合条件,此时输出符合条件的文件的绝对路径名。
实现思路:对于以空格分隔的文件名序列,我们在main函数里用for循环遍历读取,每当读取一次就做一次递归遍历操作,这样时间复杂度有点高,另一种想法是事先将文件名序列存放在一个vector里,递归遍历目录树的时候一起比较。myfunc2的实现思路和myfunc2类似,这里是比较文件名序列和目录树递归遍历的文件名是否相同,只要比较pathname(当前访问节点的路径名的最后部分)和文件名序列就可(即从最后一位开始比较)。这里我再尝试了另一种将相对路径转化为绝对路径的方法,就是先用chdir转变当前工作目录,然后用getcwd获取当前工作目录的绝对路径,这里的转化需要对pathname做一些处理,具体见myfunc3函数:
static int myfunc3(const char *pathname, const struct stat *statptr, int type)
{
int len=80,filenamelen=0,pathnamelen=0;
char *fullpathname;
if(type ==FTW_F){
//获取文件名长度
filenamelen = strlen(filename);
// printf("filenamelen = %d\n",filenamelen);
// printf("filename = %s",filename);
pathnamelen=strlen(pathname);
// printf("pathnamelen = %d\n",pathnamelen);
// printf("pathname = %s\n",pathname);
fullpathname=malloc(len+100);
for(int i=1;i<filenamelen;i++){
if(pathname[pathnamelen-i]!=filename[filenamelen-i]){
return 0;
}
}
if(pathname[pathnamelen-filenamelen-1]=='/'){
//获取文件的绝对路径名
// printf("pathname:%s\n",pathname);
int pos;
for(int i=pathnamelen-1;i>=0;i--){
if(pathname[i]=='/') {pos = i;break;}
}
// printf("pathnamelen:%d\n",pathnamelen);
// printf("pos = %d\n",pos);
char *path = (char*)malloc(sizeof(char)*pos);
strncpy(path,pathname,pos);
if(chdir(path) <0) {
printf("failed\n");
}
getcwd(fullpathname,len);
printf("%s/%s\n",fullpathname,filename);
count++;
}
}
return 0;
}
执行./work2 /home/linli/CLionProjects/ -name main.cpp 后的结果如下:
执行./work2 …/…/ -name work2.c 结果如下
实验心得
1、熟悉了文件IO函数的调用,熟悉了UNIX系统的文件系统,对UNIX系统有了更深的认识
2、练习编写makefile,增强了代码编写的能力。