今天在《Linux程序设计》中看到了一段代码,用来实现递归遍历一个目录下所有的文件并打印到标准输出(也就是屏幕终端)。因为用到了很多文件操作的函数,所以记录下来自己的学习过程。
代码内容
scan.cpp
:
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void printfdir(char *dir,int depth)
{
DIR* dp;//DIR结构指针
struct dirent *entry;//dirent结构指针
if ((dp=opendir(dir))==NULL)//打开文件失败
{
fprintf(stderr,"can not open this dir:%s\n",dir);//打印到标准错误,也就是在屏幕上输出错误信息
return;//退出
}
chdir(dir);//进入目录
while ((entry=readdir(dp))!=NULL)//循环遍历
{
if (entry->d_type==DT_DIR)//如果是目录
{
if (strcmp(".",entry->d_name)==0 || strcmp("..",entry->d_name)==0)//因为已经遍历过. ..两个目录了排除这两个目录
{
continue;
}
printf("%*s%s\n",depth," ",entry->d_name);
printfdir(entry->d_name,depth+4);//递归调用
}
else//不是目录就是文件
{
printf("%*s%s\n",depth," ",entry->d_name);
}
}
chdir("..");//返回上级目录
closedir(dp);//关闭当前目录流
}
int main(int argc,char *argv[])
{
char *topdir=".";
if (argc>=2)
{
topdir=argv[1];
}
printf("directory scan of %s:\n",topdir);
printfdir(topdir,0);
printf("done\n");
exit(0);
}
main函数
首先从main
函数看,发现跟以往不同的是多了两个参数。
C语言规定main
函数可以有两个参数,第一个形参是int
类型的变量,另一个是char **(char *[])
类型的变量。一般把这两个参数叫做argc
跟argv
。
在Linux下,argc
表示命令行的参数个数,包括执行程序的名字;argv
表示命令行的各个参数。
#include <iostream>
using namespace std;
int main(int argc,char *argv[])
{
cout<<"main function parameters'num:"<<argc<<endl;
for (int i = 0; i < argc; i++)
{
cout<<"parameter"<<i+1<<" is :"<<argv[i]<<endl;
}
return 0;
}
运行结果如下:
在scan.cpp
中main
函数将从命令行读入多个参数,如果只有一个参数,则将.
传递给printfdir
函数,也就是当前目录,如果是有多的参数,则传递第二个参数。
printfdir函数
函数入口参数有两个,char *dir
传入要遍历的目录名字,depth
表示最后程序打印出来的空格的缩排。
-
DIR* dp;
Linux下使用
DIR
结构作为目录操作的基础,在dirent.h
头文件中定义。函数原型:struct __dirstream { void *__fd; char *__data; int __entry_data; char *__ptr; int __entry_ptr; size_t __allocation; size_t __size; __libc_lock_define (, __lock) }; typedef struct __dirstream DIR;
DIR
结构的数据项则返回到dirent
结构。 -
struct dirent *entry;
dirent
结构也在dirent.h
t头文件内定义,包含目录内的内容,反应文件的一些信息,结构体中的内容如下struct dirent { __ino_t d_ino;//文件的inode节点号 __off_t d_off; unsigned short int d_reclen;//文件名字长度 unsigned char d_type; //文件类型 char d_name[256]; //文件名字 /* We must not include limits.h! */ };
-
opendir
函数函数原型:
DIR *opendir(const char *name)
形参:要打开的目录的名字
返回值:为打开的目录创建一个目录流。成功则返回一个
DIR
指针,失败返回NULL
-
chdir
函数这个命令就熟悉得不能再熟悉了,就是
cd
命令,进入一个目录 -
readdir
函数函数原型:
struct dirent*readdir(DIR *dirp)
形参:要读取的目录流
返回值:读取成功则返回
dirent
结构指针,失败或者到达目录尾则返回NULL
-
closedir
函数关闭一个目录流
-
stat结构
与S_ISDIR
宏定义在书中其实在判断一个文件名是否是目录的时候,用的是一个
stat
结构的st_mode
以及S_ISDIR
的宏定义,后来我换成用dirent
结构中的d_type
,觉得会简便点原程序部分内容如下:
...... chdir(dir); struct stat statbuff;//声明stat结构变量statbuff,存放着比dirent结构更多关于目录文件的信息 while ((entry=readdir(dp))!=NULL) { lstat(entry->d_name,&statbuff);//statbuff获取文件名,内部存储着该文件名的信息 if (S_ISDIR(statbuff.st_mode) )//通过S_ISDIR宏定义判断 { if ( strcmp(".",entry->d_name)==0 || strcmp("..",entry->d_name)==0 ) { continue; } ......
-
stat
结构定义于/sys/stat.h
头文件中,结构体中的内容如下:struct stat { mode_t st_mode; //文件对应的模式,文件,目录等 ino_t st_ino; //i-node节点号 dev_t st_dev; //设备号码 dev_t st_rdev; //特殊设备号码 nlink_t st_nlink; //文件的连接数 uid_t st_uid; //文件所有者 gid_t st_gid; //文件所有者对应的组 off_t st_size; //普通文件,对应的文件字节数 time_t st_atime; //文件最后被访问的时间 time_t st_mtime; //文件内容最后被修改的时间 time_t st_ctime; //文件状态(属性)改变时间 blksize_t st_blksize; //文件内容对应的块大小 blkcnt_t st_blocks; //文件内容对应的块数量 };
在这里用到的是第一个成员变量
st_mode
-
lstat
函数函数原型:
int lstat(const char *path,struct stat *buf)
参数:第一个是文件名,第二个是一个
stat
结构 -
判断是否是一个目录,其他类似的宏定义有
S_ISLNK是否是一个连接. S_ISREG是否是一个常规文件. S_ISDIR是否是一个目录 S_ISCHR是否是一个字符设备. S_ISBLK是否是一个块设备 S_ISFIFO是否是一个FIFO文件. S_ISSOCK是否是一个SOCKET文件.
-
运行结果
遍历当前目录
./scan
或者./scan .
遍历./home
目录
$ ./scan /home
如果觉得要遍历之后输出的内容太多可以将程序的输出重定向到另一个文本文件或者用管道命令结合more
分页查看,按q
退出也很方便。
$ ./scan /home > file1
#或者
$ ./scan /home | more
学习心得
在这种函数比较多参数又比较复杂的情况下,多看函数原型,一点点的去分析函数参数跟返回值,会更容易帮助自己快速读懂程序。