1.5 输入和输出
文件描述符
文件描述符通常是小的且非负的整数,内核使用它来标识能被特定进程访问的文件。只要打开或创建了新文件,内核就返回一个文件描述符,我们使用它来读写文件。
标准输入,标准输出和标准错误
通常,一旦一个新的程序运行,所有的shell就为其打开三个描述符:标准输入,标准输出和标准错误。如果不做什么特别的事,就像下面这样一条简单的命令:
ls
那么所有的三个描述符都连接到终端。多数shell提供了一种方式来重定向三个描述符到文件。例如:
ls > file.list
执行ls命令,并把它的标准输出重定向到名为file.list的文件。
无缓冲I/O
无缓冲I/O由open函数,read函数,write函数,lseek函数和close函数提供。这些函数都需要文件描述符才能工作。
例子
如果我们想要从标准输入读取,从标准输出写入,那么图1.4中的程序就可以拷贝UNIX系统中的常规文件。
apue.h中包含的<unistd.h>头文件,以及两个常量STDIN_FILENO和STDOUT_FILENO都是POSIX标准(下章中将会详细介绍)的一部分。在该头文件中有许多UNIX系统服务的函数的原型,例如我们调用的read和write函数。
常量STDIN_FILENO和STDOUT_FILENO在<unistd.h>中定义,它们指定了标准输入和标准输出的文件描述符。通常,STDIN_FILENO和STDOUT_FILENO的值分别为0和1,然而,在便携设备开发中,我们使用新的名字来表示它们。
3.9节中会详细讨论BUFFSIZE常量,将看到它的不同值是如何影响一个程序的效率的。尽管如此,无论BUFFSIZE为何值,图1.4中的程序都会拷贝常规文件。
read函数返回读取的字节数,这个值被用来作为要写入的字节数。当到达输入文件的末尾时,read函数返回0,程序停止。如果读取时发生错误,read函数返回-1。多数系统函数在发生错误时返回1。
如果像下面这样按标准名字(a.out)编译我们的程序
./a.out > data
标准输入就是终端,而标准输出被重定向到文件data,同时标准错误也是终端。如果这个输出文件不存在,那么shell将默认创建它。除非输入终止符(通常是Control-D),程序将拷贝输入到标准输出。
如果我们运行
./a.out < infile > outfile
那么文件finfile将被拷贝到文件outfile。
图 1.4 列出目录中的所有文件
第三章中将详细描述无缓冲I/O函数。
标准I/O
标准I/O函数为无缓冲I/O函数提供了缓冲接口。使用标准I/O可以防止在选择合适缓冲大小的时候出现错误,例如图1.4中的BUFFSIZE常量。使用标准I/O函数的另一个优点是其仅仅处理输入的行(UNIX应用程序中的通常事件)。例如fgets函数,它读取整个一行。另一方面,read函数读取指定的字节数。就像将5.4节中看到的那样,标准I/O库提供的函数让我们能够控制缓冲的使用风格。
最常见的标准I/O函数就是printf。如果在程序中调用printf函数,可以通过包含apue.h来包含<stdio.h>头文件,<stdio.h>头文件中包含了所有标准I/O函数的原型。
例子
图1.5中的程序会在5.8节中详细讨论。该程序像前面的程序那样调用read何write函数。该程序拷贝标准输入到标准输出,同时也能够拷贝任何常规文件。
getc函数一次读取一个字符,该字符被putc函数写入。在最后一个输入的字节被读取后,getc返回常量EOF(<stdio.h>中定义)。标准I/O常量stdin和stdout也在<stdio.h>头文件中定义,它们分别表示标准输入和标准输出。
图 1.5 使用标准I/O拷贝标注输入到标注输出