1.4 文件与目录
文件系统
UNIX文件系统是按层次安排目录和文件的。起始目录被称为根(root),它的名字是一个字符 / 。
目录是一个包含目录项的文件。在逻辑上,可以认为每个目录项都包含一个文件名,同时还包含说明该文件属性的信息。文件属性是:文件类型,文件长度,文件所有者,文件的许可权(例如,其他用户能否能访问该文件),文件最后的修改时间等。start和fstat函数返回一个包含所有文件属性的信息结构。第4章将详细说明文件的各种属性。
目录项事实上存储在磁盘上,对此区别于目录项的逻辑看法。多数UNIX文件系统的实现并不在目录项中存储该目录项自己的属性,因为当文件有很多硬连接的时候,很难做到同步保存该文件的属性。当我们在第四章讨论硬连接后,对此将有清晰的认识。
文件名
目录中的各个名字被称为文件名。唯一两个不能出现在文件名中的字符是反斜杠(/)和空字符(null)。反斜杠用来把文件名从路径名中区别出来,空字符用来结束一个路径名。因此,把文件名中可用的字符限制在一般打印字符的子集中是一个好习惯。(我们限制可用字符还因为,如果我们在文件名中使用了shell的特殊字符,我们就必须使用shell的引号机制来引用文件名,这将导致问题复杂化。)
被反斜杆区分开,并且可选的以反斜杆开头一个或多个文件名的序列,构成了路径名。以反斜杆起头的路径名称为绝对路径;否则,称为相对路径。相对路径涉及到的文件相对于当前目录。文件系统的根(/)的名字是绝对路径的特例,它没有文件名。
例子
列出一个目录中所有文件的名字并不困难。图1.3展示了ls(1)命令实现的本质。
ls(1)符号是UNIX系统手册的一般表示方法,用来引用特定的项。它引用第一小节中的ls项。小节号通常用数字1到8表示,同时每一小节中的所有项都是按字母顺序排列的。本书中,我们假定你有一份你的UNIX系统手册的拷贝。
历史上,UNIX系统把所有8个小节集中在UNIX程序员手册中。随着页数的增加,趋势变为把各节安排在不同的手册中:例如一份为用户准备的,一份为程序员准备的,一份为系统管理员准备的。
一些UNIX系统使用大写字母在已有的小节中进一步细分手册。举例来说,所有AT&T中的标准输出/输入(I/O)函数在3S小节中,比如fopen(3S)。其它系统用字母来代替数字表示小节号,例如用C表示命令。
今天,许多手册用电子形式进行发行。如果你的手册是联机手册,查看手册中ls命令的方式或许是这样的:
man 1 ls
或者
man –s1 ls
图1.3是一个程序,其打印出目录中各个文件的名字。如果源码文件的名字是myls.c,我们按照以下方式把它编译成默认的可执行文件a.out:
cc myls.c
cc(1)是早期的C编译器。在支持GNU C编译器的系统上,gcc(1)是C编译器。这里,cc通常与gcc相连接(译者注:原句是Here,cc is often linked to gcc。没翻译好,期待哪位大虾指教)。
一些输出例子如下:
$ ./a.out /dev
.
..
console
tty
mem
kmem
null
mouse
stdin
stdout
stderr
zero
省略其它未显示的行
cdrom
$ ./a.out /var/spool/cron
can’t open /var/spool/cron: Permission denied
$ ./a.out .dev/tty
can’t open /dev/tty: Not a directory
像上面那样,我们会以如下的方式展示运行的命令和该命令的输出:输入的字符以红色加粗字体显示,而程序的输出以红色字体显示。如果对于输出需要加注释,我们会以红色斜体字体显示注释。输入行前面的美元符号是shell打印出来的提示符。我们始终使用美元符号作为shell提示符。
注意文件名并没有按照字母顺序输出。而ls命令会在打印名字之前现对名字排序。
在这个20行的程序中,许多细节应当被考虑:
l 首先,我们包含了一个我们自己编写的头文件:apue.h。几乎本书中所有的程序都包含这个头文件。该头文件包含了一些标准系统头文件,并且定义了本书例子中使用的数值常量和函数原型。该头文件的清单在附录B中。
l main函数的声明使用了ISO C标准的风格。(下章中我们会谈到更多ISO C标准。)
l 我们从命令行得到参数argv[1]作为对象目录的名字。在第七章,我们将看到main函数是怎样被调用的,同时看到命令行参数和环境变量是如何传递给程序的。
l 由于不同UNIX系统的目录项格式不尽相同,我们使用opendir函数,readdir函数和closedir函数来操作目录。
l opendir函数返回DIR结构的指针,并传递该指针给readdir函数。不必关心DIR结构的细节。接着在循环中调用readdir函数,用来读取每个目录项。readdir函数返回一个dirent结构的指针,否则,在无目录项可读时返回null指针。我们只需要检查dirent结构中每个目录项的名字(d_name)。使用这个名字,我们就可以调用stat函数(4.2节介绍)来确定文件的所有属性。
l 我们调用两个我们自己编写的函数来处理错误:err_sys和err_quit。从图1.3的输出中我们可以看到,err_sys函数打印出了丰富的信息来描述遇到的错误(“Permission denied”和“Not a directory”)。附录B中列出了这两个错误处理函数。在1.7节中我们将更详细的谈到错误处理。
l 当程序完成时,以参数0调用exit函数。exit函数结束一个程序。通常,参数0意味者正常结束,而1到255之间的参数意味着发生了错误。8.5节中,我们将会学习一个程序(比如shell或者我们自己写的程序)如何获得另一个正在执行中的程序的exit状态。
图 1.3 列举出一个目录中的所有文件
工作目录
每个进程都有一个工作目录,有时又称为当前工作目录。所有的相对路径都从该目录开始。一个进程能够用chdir函数改变它的工作目录。
例如,相对路径doc/memo/joe指出memo目录中的文件或目录joe,memo目录又在doc目录中,而doc一定是工作目录下的一个目录。查看这个相对路径,我们知道doc和memo一定是目录,但是我们不能确定joe是一个文件还是一个目录。路径/usr/lib/lint是一个绝对路径,它指出了lib目录中的文件或目录lint,lib目录在usr目录中,而usr目录又在根(root)目录中。
起始(Home)目录
当我们登录时,工作目录被设定为我们的起始目录。我们的起始目录是从密码文件(1.3节介绍)中我们的登录项得到的。