目录与文件属性:编写ls
问题
ls命令可以列出目录中所有文件的名字,以及这些文件的其它信息。注意,ls对文件和目录所做的操作是不同的,ls能判定参数指定的是文件还是目录。如果要自己编写ls,需要掌握三点:
- 如何列出目录的内容。
- 如何读取并显示文件的属性。
- 给出一个名字,如何能够判断出它是目录还是文件。
编写ls
目录是一种特殊的文件,它的内容是文件和目录的名字。与普通文件不同的是,目录文件永远不会为空,每个目录至少包含两个特殊的项目:.和..,分别代表当前目录和上一级目录。
什么函数可以读目录?在联机帮助中根据关键字direct来查找答案,用grep过滤出包含read的主题:

其中readdir (3)正是需要的,查看它readdir的联机帮助:

The readdir_r() function is a reentrant version of readdir(). It reads the next directory entry from the directory stream dirp, and returns it in the caller-allocated buffer pointed to by entry. (See NOTES for information on allocating this buffer.) A pointer to the returned item is placed in *result; if the end of the directory stream was encountered, then NULL is instead returned in *result.
dirent结构中的成员d_name存放文件名,于是,可以开始编写ls。
ls的算法如下:
main()
opendir
while(readdir)
print d_name
closedir
编写ls -l
ls -l要做两件事情,一是列出目录的内容,二是显示文件的详细信息,这实际上是两件不同的工作,目录包含文件名,文件信息则需要从另外的途径获得。先来看ls -l的输出:

每行都包含7个字段:模式(mode)、链接数(links)、文件所有者(owner)、组(group)、大小(size)、最后修改时间(last-modified)和文件名(name)。如何得到文件的信息?依旧通过联机帮助:

可以看到提取文件状态的系统调用为stat (2),继续查看stat (2)的联机帮助:

stat把文件path的信息复制到stat结构的buf中,程序可以从buf中获取文件的属性,联机帮助中说明了struct stat的成员变量:

如果仅仅是把stat中的这些信息打印出来,基本上已经实现了ls -l,但是存在显示的问题:打印出来的mode是一个八进制数,所属用户和所属组都是以id的形式显示。因此,为了完善ls -l,需要进一步处理模式、用户名和组的显示。
将模式字段转换成字符串
st_mode是一个16位的二进制数,文件类型和权限被编码在这个数中。其中前4位用作文件类型。接下来3位是文件的特殊属性,1代表具有某个属性,0代表没有,这3位分别是set-user-ID位、set-group-ID位和sticky位。最后的9位是许可权限,分为3组,对应于3种用户:文件所有者、同组用户和其它用户。
文件类型可以通过掩码来将其它部分置0,从而得到文件类型的值,在<sys/stat.h>中有如下定义:
#ifndef _POSIX_SOURCE
#define S_IFMT 0170000 /* type of file mask */
#define S_IFIFO 0010000 /* named pipe (fifo) */
#define S_IFCHR 0020000 /* character special */
#define S_IFDIR 0040000 /* directory */
#define S_IFBLK 0060000 /* block special */
#define S_IFREG 0100000 /* regular */
#define S_IFLNK 0120000 /* symbolic link */
#define S_IFSOCK 0140000 /* socket */
#define S_ISVTX 0001000 /* save swapped text even after use */
#endif
S_IFMT是一个掩码,它的值是0170000,可以用来过滤出前4位表示的文件类型。S_IFREG代表普通文件,S_IFDIR代表目录文件。通过掩码把其它无关的部分置0,再与表示目录的代码比较,从而判定是否是一个目录。更简单的方法是使用<sys/stat.h>中的宏来判断。
#define S_ISDIR(m) ((m & 0170000) == 0040000) /* directory */
#define S_ISCHR(m) ((m & 0170000) == 0020000) /* char special */
#define S_ISBLK(m) ((m & 0170000) == 0060000) /* block special */
#define S_ISREG(m) ((m & 0170000) == 0100000) /* regular file */
将用户/组ID转换成字符串
我们知道,在/etc/passwd中包含着用户列表,但是搜索文件显然不合理。查看联机帮助,可以通过库函数getpwuid来得到完整的用户列表。

getpwuid需要uid作为参数,返回一个指向struct passwd的指针,passwd这个结构体定义在/usr/include/pwd.h中。

类似的,用户所属的组信息保存在/etc/group中,通过getgrgid来访问组列表。联机文档如下所示。

实现
ls和ls -l的实现代码见Github。
总结
-
Unix将数据存放于文件中,目录是特殊的文件,它的内容就是其中的文件和子目录的名字,还包含自己的名字,Unix提供一系列函数对目录操作,打开、读、定位和关闭等,但是不提供写目录的函数。 -
每个文件都有很多属性,程序通过系统调用
stat来得到文件的属性。 -
Unix提供的系统调用和库函数众多,要学会查阅联机帮助。