Linux系统下有一系列对目录操作的API,如果单一的去学习这些API那么学习过程无疑是单调的。本文以实现Linux下ls命令为例,简单说明与目录操作相关的API。
1.获取文件名
获取一个指定目录下的文件名列表是ls命令最基本的功能。对于一个普通文件而言,读文件中数据的步骤是:open()文件,read()文件,close()文件。对于目录文件而言,它包含的内容就是若干个目录项,一个目录项即对应该目录下的一个文件。因此读目录的过程与普通文件类似,不过Linux下的目录是一种特殊的文件,因此有一组针对目录操作的API,具体如下:
#include < sys/types.h > #include < dirent.h > DIR *opendir(const char *name); struct dirent *readdir(DIR *dirp); int closedir(DIR *dirp);
读目录之前必须用opendir()打开目录,它返回一个DIR *类型的目录流,它类似于文件描述符。接着使用readdir()读取该目录流中的目录项,该函数每成功调用一次则返回一个struct dirent类型的目录项。当打开某个目录后,第一次调用该函数则返回当前目录下第一个文件的信息,第二次调用则返回第二个文件的信息,以此类推。当读完该目录下的所有文件后返回NULL。由此可见,要读取指定目录下的所有目录项,则需要一个循环的过程。描述目录项的结构体dirent描述如下:
struct dirent { ino_t d_ino; /* inode number */ off_t d_off; /* offset to the next dirent */ unsigned short d_reclen; /* length of this record */ unsigned char d_type; /* type of file; not supported by all file system types */ char d_name[256]; /* filename */ };
目录项与目录是两个完全不同的概念。比如在/home/edsionte/test/下有一个目录文件mydir和一个文本文件mytxt.txt,那么该目录下就包含两个目录项,分别描述mydir和mytxt.txt。但是通过这两个目录项并不能直接就访问到对应文件,因为目录项只用于描述当前目录下的文件名等信息。如果要访问这两个文件,还必须获得/、home/、edsionte/和test/这几个目录项。由此可见,目录项是用来方便搜索文件的结构。
当不再使用某个已打开的目录时,使用closedir()关闭该目录。通过上述三个API就可以实现获取指定目录下文件名的功能。伪代码实现方法如下:
int get_file_name(char *path) { DIR *mydir; struct dirent *myentry; if ((mydir = opendir(path)) == NULL) { do_error(); } while ((myentry = readdir(mydir)) != NULL) { printf("%s\t", myentry->d_name); } printf("\n"); closedir(mydir); return 0; }
按照这种方式实现的ls命令只能显示path目录下的文件名称,仅此而已。
2.获取文件列表
获取指定目录下文件名对于ls命令来说还远远不够,我们需要获取每个文件名的属性信息,也就是实现类似ls -l的功能。要实现该功能就必须使用stat族函数,该函数会将文件的信息返回到传入的参数buf中,buf是struct stat类型。
#include < sys/types.h > #include < sys/stat.h > #include < unistd.h > int stat(const char *path, struct stat *buf); int fstat(int fd, struct stat *buf); int lstat(const char *path, struct stat *buf); struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ };
得到了每个文件的stat结构,就可以按照需求显示文件的属性信息。对于stat族函数,不管使用哪一个都必须获得文件的绝对路径。在本文第一部分中通过读取指定目录下的目录项,只能通过d_name字段获得目录项名字,因此使用stat函数之前必须合成每个文件的绝对路径。获取文件列表的实现方法如下:
int get_file_name(char *path) { DIR *mydir; struct dirent *myentry; if ((mydir = opendir(dirpath)) == NULL) { do_error(); } while ((myentry = readdir(mydir)) != NULL) { printf("%s\t", myentry->d_name); strcat(tmpbuf, dirpath); strcat(tmpbuf, myentry->d_name); struct stat myfstat; if (stat(tmpbuf, &myfstat) == -1) { do_error(); } print_file_stat(myfstat); printf("\n"); memset(tmpbuf, len); } closedir(mydir); return 0; }
其中,print_file_stat函数可以按照你的具体需求显示当前文件的各种属性,比如关于文件的各种时间,文件大小等。本质上就是将dirent结构中的相关字段打印。
3. 是否可以切换目录
Linux下每个文件都有读写执行(RWX)三个权限。对于普通文件来说,R权限代表可以读文件,W权限代表可以对文件进行修改,X权限代表可以执行该文件(与是否执行成功无关)。对于目录而言,读写执行的含义如下:
R:可以读取该目录下的文件名
W:可以增加、删除、修改或移动该目录下的文件名
X:可以切换到该目录下
这里比较不易理解的是X权限,它并不是指目录具有“执行”权限,而是表示当前进程可以切换到该目录下。
如果某个用户对一个目录只有R权限,那么这个用户只能ls到该目录下的文件名,甚至连ls -l都不能成功执行。通过上述文件列表的实现过程可知,如果要获取每个文件的属性信息则必须能够成功访问到该文件的绝对路径。但是现在没有X权限,进程并不能切换到该目录下,因此也就访问失败了。与此类似,如果要修改一个目录,没有X权限也是不能成功执行的。如果某个用户对一个目录只有执行权限,那么除了可以切换到该目录外,什么也做不了。
根据此原理,我们对ls命令增加一个新的功能:判断当前执行ls命令的用户是否可以切换到该目录下子目录。我们并不依次去判断子目录的权限,通过chdir()就可以完成。
#include < unistd.h > int chdir(const char *path);
chdir()用于将当前工作目录切换到path指定的目录,如果切换成功则返回0,否则返回-1。据此,修改后的ls命令实现方法如下:
int get_file_name(char *path) { DIR *mydir; struct dirent *myentry; if ((mydir = opendir(dirpath)) == NULL) { do_error(); } while ((myentry = readdir(mydir)) != NULL) { printf("%s\t", myentry->d_name); strcat(tmpbuf, dirpath); strcat(tmpbuf, myentry->d_name); struct stat myfstat; if (stat(tmpbuf, &myfstat) == -1) { do_error(); } print_file_stat(myfstat); if (chdir(tmpbuf) == -1) { print_no_chdir(); } else { print_chdir(); } printf("\n"); memset(tmpbuf, len); } closedir(mydir); return 0; }
如果切换成功,通过print_no_chdir()打印可切换标志,否则打印不可切换标志。上述的ls实现方式只是基本模型,感兴趣的童鞋可以在此基础上加以改进。