问题

ls命令可以列出目录中所有文件的名字,以及这些文件的其它信息。注意,ls对文件和目录所做的操作是不同的,ls能判定参数指定的是文件还是目录。如果要自己编写ls,需要掌握三点:

  • 如何列出目录的内容。
  • 如何读取并显示文件的属性。
  • 给出一个名字,如何能够判断出它是目录还是文件。

编写ls

目录是一种特殊的文件,它的内容是文件和目录的名字。与普通文件不同的是,目录文件永远不会为空,每个目录至少包含两个特殊的项目:...,分别代表当前目录和上一级目录。

什么函数可以读目录?在联机帮助中根据关键字direct来查找答案,用grep过滤出包含read的主题:

Alt text

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

Alt text

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的输出:

Alt text

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

Alt text

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

Alt text

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

Alt text

如果仅仅是把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来得到完整的用户列表。

Alt text

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

Alt text

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

Alt text

实现

lsls -l的实现代码见Github

总结

  • Unix将数据存放于文件中,目录是特殊的文件,它的内容就是其中的文件和子目录的名字,还包含自己的名字,Unix提供一系列函数对目录操作,打开、读、定位和关闭等,但是不提供写目录的函数。

  • 每个文件都有很多属性,程序通过系统调用stat来得到文件的属性。

  • Unix提供的系统调用和库函数众多,要学会查阅联机帮助。