目录与文件属性:编写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
提供的系统调用和库函数众多,要学会查阅联机帮助。